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 }