-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chrome: Persist the state of the sidebar across page refresh #2462
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2462 +/- ##
==========================================
+ Coverage 26.98% 27.37% +0.39%
==========================================
Files 159 161 +2
Lines 4903 4968 +65
Branches 817 830 +13
==========================================
+ Hits 1323 1360 +37
- Misses 3035 3055 +20
- Partials 545 553 +8
Continue to review full report at Codecov.
|
Hoooraaay! It's like my birthday! I love love this, and it works great! |
editor/selectors.js
Outdated
* Returns true if the editor sidebar panel is open, or false otherwise. | ||
* | ||
* @param {Object} state Global application state | ||
* @return {Boolean} Whether sidebar is open | ||
*/ | ||
export function isEditorSidebarOpened( state ) { | ||
return state.isSidebarOpened; | ||
return state.preferences.isSidebarOpened; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Should this compose from getPreferences
, i.e. return getPreferences( state ).isSidebarOpened;
? Should we have a getPreference
selector? Maybe I'm being premature? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not :)
@@ -0,0 +1,64 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for splitting off this file. I've been wanting to separate reducers from store initialization for a while 👍
editor/store.js
Outdated
* Loads the initial preferences and saves them to the local storage once changed | ||
* @param {Object} store Redux Store | ||
*/ | ||
function setupStorePersistence( store ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, maybe getting ahead of myself, but thinking about how this might be implemented for maintainability. Redux Persist seems to be the canonical reference here, though I think it's overkill for what we need. That said, maybe their patterns of reducer key whitelisting and rehydration actions could serve as good inspiration?
Also, this seems to fall under the pattern of what I'd consider to be a store enhancer: http://redux.js.org/docs/api/createStore.html . It could fit well within what we're already creating as an enhancers
array below (a store enhancer accepts createStore
and returns a new createStore
, example).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we test this behavior? Seems like it wouldn't be too hard to mock/spy localStorage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely, thanks for mentioning this. I extracted the behaviour into a generic simple store enhancer allowing to persist and autohydrate a specific reducer key.
It could be easily extended later (or replaced by redux persist if needed).
editor/test/reducer.js
Outdated
|
||
it( 'should update preferences', () => { | ||
const prefs = { awesome: true }; | ||
const state = preferences( { isSidebarOpened: false }, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we apply deepFreeze
to original state to guard against mutations?
Should |
Should we have used existing user settings APIs instead of localStorage to save/retrieve preferences? |
1b6aaf0
to
196a284
Compare
Let's keep this PR as a bootstrap PR for preferences persistence and leave this for later. Also thinking about using this to store the open/closed status of the sidebar panels.
Yes probably but AFAIK this endpoint does not exist yet (see #1455). Local storage is an acceptable v1 for me. |
Sorry, I should have avoided the word API as it's overused 😄 I meant the It was previously used in the stats tracking code: https://github.com/WordPress/gutenberg/pull/2140/files#diff-17b4c77ddf8f9543bfb9ff3e64b0a6dbR23 |
:) I was not aware of these functions. Should we declare a script dependency or we just assume these functions are available for WP code? |
@aduth It looks like we can't use this API to store complex JSON object. It's for scalar values only. If I use JSON.stringify/JSON.parse, it looks like the string is sanitized or something. |
Yeah, digging into it a bit more, I don't know that the https://github.com/WordPress/wordpress-develop/blob/7157569/src/wp-includes/js/utils.js#L151-L184 I'm wondering if it might be worth pursuing (separately) some improvements to this function to use either localStorage or indexedDB, and to accept more than simple scalar values for storage. In the meantime, I think it would be appropriate to stick to localStorage, though I'm still concerned about the "logout" case. Do you have any ideas for how we could address this, here or separately? |
An option would be to add a "logout" hook and clear the localStorage key, the only concern is the duplication of the storage key constant in PHP. |
196a284
to
f17cbd0
Compare
@aduth It looks like the User Settings API rely on a global |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good 👍
const newStateValue = store.getState()[ reducerKey ]; | ||
if ( newStateValue !== currentStateValue ) { | ||
currentStateValue = newStateValue; | ||
window.localStorage.setItem( storageKey, JSON.stringify( currentStateValue ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently this is probably fine since we're not expecting the specific state value to change very often, but this might be something worth debouncing or throttling in the future.
🎉💓💓💓🤘 |
Should mobile be exempt from this persistence? Noting while switching to emulation that the sidebar being opened sticks across page loads for mobile, which while unlikely to occur in real-world usage seems odd (i.e. open sidebar on mobile, refresh page, would expect sidebar to be closed). |
I agree with 👆 |
This PR bootstrap the work to persist some UI preferences locally. (It uses localStorage)
For now, this persists only the sidebar state (opened/closed) but could be updated later to include the state of the difference sidebar panels.
solves #450 partially