github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/redux/localsettings.ts (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  /**
    12   * The local settings reducer is designed to store local-only UI settings in
    13   * redux state. These settings are maintained within a session, but not saved
    14   * between sessions.
    15   *
    16   * This is appropriate for use by components which have some local state that is
    17   * not relevant to any other components in the application; for example, the
    18   * sort setting of a table. If a value is shared by multiple components,
    19   * it should be given the full redux treatment with unique modification actions.
    20   */
    21  
    22  import _ from "lodash";
    23  import { createSelector, Selector } from "reselect";
    24  import { Action } from "redux";
    25  import { call, takeEvery } from "redux-saga/effects";
    26  
    27  import { PayloadAction } from "src/interfaces/action";
    28  
    29  const STORAGE_PREFIX = "cockroachui";
    30  const SET_UI_VALUE = `${STORAGE_PREFIX}/ui/SET_UI_VALUE`;
    31  
    32  export interface LocalSettingData {
    33    key: string;
    34    value: any;
    35  }
    36  
    37  /**
    38   * Local settings are stored in a simple string-keyed dictionary.
    39   */
    40  export interface LocalSettingsState {
    41    [key: string]: any;
    42  }
    43  
    44  /**
    45   * Persist local setting value in sessionStorage.
    46   * Append STORAGE_PREFIX to organize keys in a group.
    47   */
    48  function saveToSessionStorage(data: LocalSettingData) {
    49    const value = JSON.stringify(data.value);
    50    // Silently handle possible exception when saving data to sessionStorage.
    51    // It is possible that sessionStorage is full, so it is not
    52    // possible to persist data in it.
    53    try {
    54      sessionStorage.setItem(`${STORAGE_PREFIX}/${data.key}`, value);
    55    } catch (e) {
    56      console.warn(e.message);
    57    }
    58  }
    59  
    60  /**
    61   * Retrieve local setting value by key from sessionStorage.
    62   * Value is stored as a stringified JSON so has to be parsed back.
    63   */
    64  function getValueFromSessionStorage(key: string) {
    65    const value = sessionStorage.getItem(`${STORAGE_PREFIX}/${key}`);
    66    return JSON.parse(value);
    67  }
    68  
    69  /**
    70   * reducer function which handles local settings, storing them in a dictionary.
    71   */
    72  export function localSettingsReducer(state: LocalSettingsState = {}, action: Action): LocalSettingsState {
    73    if (_.isNil(action)) {
    74      return state;
    75    }
    76  
    77    switch (action.type) {
    78      case SET_UI_VALUE:
    79        const { payload } = action as PayloadAction<LocalSettingData>;
    80        state = _.clone(state);
    81        state[payload.key] = payload.value;
    82        return state;
    83      default:
    84        return state;
    85    }
    86  }
    87  
    88  /**
    89   * Action creator to set a named local setting.
    90   */
    91  export function setLocalSetting(key: string, value: any): PayloadAction<LocalSettingData> {
    92    return {
    93      type: SET_UI_VALUE,
    94      payload: {
    95        key: key,
    96        value: value,
    97      },
    98    };
    99  }
   100  
   101  /**
   102   * LocalSetting is a wrapper class which provides type safety when accessing UI
   103   * settings. Components that use a local setting should instantiate this class
   104   * to access or modify it.
   105   */
   106  export class LocalSetting<S, T> {
   107    private _value: Selector<S, T>;
   108  
   109    /**
   110     * Action creator which will create or overwrite this setting when dispatched
   111     * @param value The new value of the setting
   112     */
   113    set = (value: T) => {
   114      return setLocalSetting(this.key, value);
   115    }
   116  
   117    /**
   118     * Selector which retrieves this setting from the LocalSettingsState
   119     * @param state The current top-level redux state of the application.
   120     */
   121    selector = (state: S) => {
   122      return this._value(state);
   123    }
   124  
   125    /**
   126     * Construct a new LocalSetting manager.
   127     * @param key The unique key of the setting.
   128     * @param innerSelector A selector which retrieves the LocalSettingsState from
   129     * the top-level redux state of the application.
   130     * @param defaultValue Optional default value of the setting when it has not
   131     * yet been set.
   132     */
   133    constructor(public key: string, innerSelector: Selector<S, LocalSettingsState>, defaultValue?: T) {
   134      this._value = createSelector(
   135        innerSelector,
   136        () => getValueFromSessionStorage(this.key),
   137        (uiSettings, cachedValue) => {
   138          return uiSettings[this.key] || cachedValue || defaultValue;
   139        },
   140      );
   141    }
   142  }
   143  
   144  export function* persistLocalSetting(action: PayloadAction<LocalSettingData>) {
   145    yield call(saveToSessionStorage, action.payload);
   146  }
   147  
   148  export function* localSettingsSaga() {
   149    yield takeEvery(SET_UI_VALUE, persistLocalSetting);
   150  }