From 083a11a56370c289d89221491606bb26aee72fd0 Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 31 Mar 2020 14:04:10 -0400 Subject: [PATCH] feat: store state in localStorage --- ui/package-lock.json | 5 ++ ui/package.json | 1 + ui/src/App.js | 88 +++++++++++++++++++------------- ui/src/album/albumState.js | 6 +-- ui/src/store/createAdminStore.js | 59 +++++++++++++++++++++ ui/src/store/persistState.js | 20 ++++++++ 6 files changed, 140 insertions(+), 39 deletions(-) create mode 100644 ui/src/store/createAdminStore.js create mode 100644 ui/src/store/persistState.js diff --git a/ui/package-lock.json b/ui/package-lock.json index f3e09c97b..aac132fa2 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -10436,6 +10436,11 @@ "lodash._reinterpolate": "^3.0.0" } }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", diff --git a/ui/package.json b/ui/package.json index faefe0c08..9f3e817db 100644 --- a/ui/package.json +++ b/ui/package.json @@ -8,6 +8,7 @@ "@testing-library/user-event": "^10.0.0", "deepmerge": "^4.2.2", "jwt-decode": "^2.2.0", + "lodash.throttle": "^4.1.1", "md5-hex": "^3.0.1", "prop-types": "^15.7.2", "ra-data-json-server": "^3.3.1", diff --git a/ui/src/App.js b/ui/src/App.js index ec968190a..8a56fef55 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -1,4 +1,6 @@ import React from 'react' +import { Provider } from 'react-redux' +import { createHashHistory } from 'history' import { Admin, resolveBrowserLocale, Resource } from 'react-admin' import dataProvider from './dataProvider' import authProvider from './authProvider' @@ -15,12 +17,15 @@ import { Player, playQueueReducer } from './audioplayer' import { albumViewReducer } from './album/albumState' import customRoutes from './routes' import themeReducer from './configuration/themeReducer' +import createAdminStore from './store/createAdminStore' const i18nProvider = polyglotI18nProvider( (locale) => (messages[locale] ? messages[locale] : messages.en), resolveBrowserLocale() ) +const history = createHashHistory() + const App = () => { try { const appConfig = JSON.parse(window.__APP_CONFIG__) @@ -32,44 +37,57 @@ const App = () => { } catch (e) {} return ( - - {(permissions) => [ - , - , - , - , - permissions === 'admin' ? ( - - ) : null, - , - permissions === 'admin' ? ( + + {(permissions) => [ , + , + , + , + permissions === 'admin' ? ( + + ) : null, + - ) : ( - - ), - - ]} - + />, + permissions === 'admin' ? ( + + ) : ( + + ), + + ]} + + ) } diff --git a/ui/src/album/albumState.js b/ui/src/album/albumState.js index 6b9f62af2..4a0def4e6 100644 --- a/ui/src/album/albumState.js +++ b/ui/src/album/albumState.js @@ -22,8 +22,8 @@ const selectAlbumList = (mode) => ({ type: mode }) const albumViewReducer = ( previousState = { - mode: localStorage.getItem('albumViewMode') || ALBUM_MODE_LIST, - list: localStorage.getItem('albumListType') || ALBUM_LIST_ALL, + mode: ALBUM_MODE_LIST, + list: ALBUM_LIST_ALL, params: { sort: {}, filter: {} } }, payload @@ -32,14 +32,12 @@ const albumViewReducer = ( switch (type) { case ALBUM_MODE_GRID: case ALBUM_MODE_LIST: - localStorage.setItem('albumViewMode', type) return { ...previousState, mode: type } case ALBUM_LIST_ALL: case ALBUM_LIST_RANDOM: case ALBUM_LIST_NEWEST: case ALBUM_LIST_RECENT: case ALBUM_LIST_STARRED: - localStorage.setItem('albumListType', type) return { ...previousState, list: type, params: albumListParams[type] } default: return previousState diff --git a/ui/src/store/createAdminStore.js b/ui/src/store/createAdminStore.js new file mode 100644 index 000000000..985e11fde --- /dev/null +++ b/ui/src/store/createAdminStore.js @@ -0,0 +1,59 @@ +import { applyMiddleware, combineReducers, compose, createStore } from 'redux' +import { routerMiddleware, connectRouter } from 'connected-react-router' +import createSagaMiddleware from 'redux-saga' +import { all, fork } from 'redux-saga/effects' +import { adminReducer, adminSaga, USER_LOGOUT } from 'react-admin' +import throttle from 'lodash.throttle' +import { loadState, saveState } from './persistState' + +export default ({ + authProvider, + dataProvider, + history, + customReducers = {} +}) => { + const reducer = combineReducers({ + admin: adminReducer, + router: connectRouter(history), + ...customReducers + }) + const resettableAppReducer = (state, action) => + reducer(action.type !== USER_LOGOUT ? state : undefined, action) + + const saga = function* rootSaga() { + yield all([adminSaga(dataProvider, authProvider)].map(fork)) + } + const sagaMiddleware = createSagaMiddleware() + + const composeEnhancers = + (process.env.NODE_ENV === 'development' && + typeof window !== 'undefined' && + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ + trace: true, + traceLimit: 25 + })) || + compose + + const persistedState = loadState() + const store = createStore( + resettableAppReducer, + persistedState, + composeEnhancers(applyMiddleware(sagaMiddleware, routerMiddleware(history))) + ) + + store.subscribe( + throttle(() => { + const state = store.getState() + saveState({ + theme: state.theme, + // queue: state.queue, TODO: Need to make queue serializable (remove functions from it) + albumView: state.albumView + }) + }), + 1000 + ) + + sagaMiddleware.run(saga) + return store +} diff --git a/ui/src/store/persistState.js b/ui/src/store/persistState.js new file mode 100644 index 000000000..992877aa9 --- /dev/null +++ b/ui/src/store/persistState.js @@ -0,0 +1,20 @@ +export const loadState = () => { + try { + const serializedState = localStorage.getItem('state') + if (serializedState === null) { + return undefined + } + return JSON.parse(serializedState) + } catch (err) { + return undefined + } +} + +export const saveState = (state) => { + try { + const serializedState = JSON.stringify(state) + localStorage.setItem('state', serializedState) + } catch (err) { + // Ignore write errors + } +}