All files / theme si-theme-store.ts

100% Statements 50/50
100% Branches 14/14
100% Functions 10/10
100% Lines 50/50

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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174                                                                                                                                  1x                     928x 928x       914x 1x     913x 913x 1x   912x         3x 1x     2x 2x 1x 1x 1x   1x         2x 1x     1x 1x 1x 1x       913x 1x   912x 912x       2x 1x     1x 1x 1x 1x       3x 1x   2x 2x       2x 1x     1x 1x 1x 1x   1x 1x       1832x 1832x 6x   1826x         4x      
/**
 * Copyright (c) Siemens 2016 - 2025
 * SPDX-License-Identifier: MIT
 */
import { Observable, of } from 'rxjs';
 
import { Theme } from './si-theme.model';
 
/**
 * SiThemeStore object is used by the theme service to load and
 * store the themes. You can inject your own implementation to provide
 * a backend implementation. Otherwise a localStorage based implementation
 * is used.
 */
export abstract class SiThemeStore {
  /**
   * Load and return an alternative custom theme, other then
   * the default element theme. This method is invoked initially
   * to check for an alternative custom theme.
   * @returns The active theme to be used, or undefined if the
   * default element theme shall be used. All wrapped in an observable
   * that can also emit errors.
   */
  abstract loadActiveTheme(): Observable<Theme | undefined>;
  /**
   * Sets the theme with the given name to active.
   * @param name - The name of the theme to become active.
   * @returns True on success, otherwise false.
   */
  abstract activateTheme(name: string): Observable<boolean>;
  /**
   * Deactivate any active theme and makes the element theme the default one.
   * @returns True on success, otherwise false.
   */
  abstract deactivateTheme(): Observable<boolean>;
  /**
   * Load and return the available theme names.
   * @returns An `Observable` of available theme names, other than the
   * default element theme.
   */
  abstract loadThemeNames(): Observable<string[]>;
  /**
   * Load theme with the given `name`.
   * @param name - The name of the theme to be returned.
   * @returns Observable with the named theme or `undefined` if no such theme exist.
   */
  abstract loadTheme(name: string): Observable<Theme | undefined>;
  /**
   * Saves a theme to the store. The name shall not be empty. A theme with
   * identical name gets overwritten.
   * @param theme - The theme to be saved.
   * @returns True on success, otherwise false. All nicely wrapped in
   * an observable that may also emit errors.
   */
  abstract saveTheme(theme: Theme): Observable<boolean>;
  /**
   * Deletes the theme with the given name from the store.
   * @param name - The name of the theme to be deleted.
   * @returns True on success, otherwise false. All nicely wrapped in
   * an observable that may also emit errors. Returns false,
   * if the theme does not exist.
   */
  abstract deleteTheme(name: string): Observable<boolean>;
}
 
export const SI_THEME_LOCAL_STORAGE_KEY = 'si-themes';
 
export interface ThemeStorage {
  activeTheme: string | undefined;
  themes: { [key: string]: Theme };
}
 
export class SiDefaultThemeStore extends SiThemeStore {
  private isBrowser: boolean;
 
  constructor(isBrowser: boolean) {
    super();
    this.isBrowser = isBrowser;
  }
 
  loadActiveTheme(): Observable<Theme | undefined> {
    if (!this.isBrowser) {
      return of(undefined);
    }
 
    const store = this.loadStore();
    if (store.activeTheme) {
      return of(store.themes[store.activeTheme]);
    } else {
      return of(undefined);
    }
  }
 
  activateTheme(name: string): Observable<boolean> {
    if (!this.isBrowser) {
      return of(false);
    }
 
    const store = this.loadStore();
    if (store.themes[name]) {
      store.activeTheme = name;
      this.saveStore(store);
      return of(true);
    } else {
      return of(false);
    }
  }
 
  deactivateTheme(): Observable<boolean> {
    if (!this.isBrowser) {
      return of(false);
    }
 
    const store = this.loadStore();
    store.activeTheme = undefined;
    this.saveStore(store);
    return of(true);
  }
 
  loadThemeNames(): Observable<string[]> {
    if (!this.isBrowser) {
      return of([]);
    }
    const store = this.loadStore();
    return of(Array.from(Object.keys(store.themes)));
  }
 
  saveTheme(theme: Theme): Observable<boolean> {
    if (!this.isBrowser) {
      return of(false);
    }
 
    const store = this.loadStore();
    store.themes[theme.name] = theme;
    this.saveStore(store);
    return of(true);
  }
 
  loadTheme(name: string): Observable<Theme | undefined> {
    if (!this.isBrowser) {
      return of(undefined);
    }
    const store = this.loadStore();
    return of(store.themes[name]);
  }
 
  deleteTheme(name: string): Observable<boolean> {
    if (!this.isBrowser) {
      return of(false);
    }
 
    const store = this.loadStore();
    delete store.themes[name];
    if (store.activeTheme === name) {
      store.activeTheme = undefined;
    }
    this.saveStore(store);
    return of(true);
  }
 
  private loadStore(): ThemeStorage {
    const storeStr = localStorage.getItem(SI_THEME_LOCAL_STORAGE_KEY);
    if (storeStr) {
      return JSON.parse(storeStr) as ThemeStorage;
    } else {
      return { activeTheme: undefined, themes: {} };
    }
  }
 
  private saveStore(store: ThemeStorage): void {
    localStorage.setItem(SI_THEME_LOCAL_STORAGE_KEY, JSON.stringify(store));
  }
}