Skip to content

Commit

Permalink
Data module: Add built-in support for persisting stores (#8146)
Browse files Browse the repository at this point in the history
* Data module: Add built-in support for persisting stores

* Remove useless storage key

* Don't repersist if the persisted changes are kept identical

* Restoring deprecated APIs

* Avoid using window.localStorage when loading the persist file

* Fix inline-tokens registration

* Fix with-history Higher-order reducer to be stateless
  • Loading branch information
youknowriad committed Jul 24, 2018
1 parent b6ca3c1 commit f508995
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 194 deletions.
9 changes: 8 additions & 1 deletion edit-post/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { registerCoreBlocks } from '@wordpress/core-blocks';
import { render, unmountComponentAtNode } from '@wordpress/element';
import { dispatch } from '@wordpress/data';
import { dispatch, setupPersistence } from '@wordpress/data';
import deprecated from '@wordpress/deprecated';

/**
Expand All @@ -15,6 +15,11 @@ import store from './store';
import { initializeMetaBoxState } from './store/actions';
import Editor from './editor';

/**
* Module Constants
*/
const STORAGE_KEY = `WP_EDIT_POST_DATA_${ window.userSettings.uid }`;

/**
* Reinitializes the editor after the user chooses to reboot the editor after
* an unhandled error occurs, replacing previously mounted editor element using
Expand Down Expand Up @@ -88,3 +93,5 @@ export { default as PluginPostStatusInfo } from './components/sidebar/plugin-pos
export { default as PluginPrePublishPanel } from './components/sidebar/plugin-pre-publish-panel';
export { default as PluginSidebar } from './components/sidebar/plugin-sidebar';
export { default as PluginSidebarMoreMenuItem } from './components/header/plugin-sidebar-more-menu-item';

setupPersistence( STORAGE_KEY );
13 changes: 3 additions & 10 deletions edit-post/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
*/
import {
registerStore,
withRehydration,
loadAndPersist,
restrictPersistence,
} from '@wordpress/data';

/**
Expand All @@ -14,20 +13,14 @@ import reducer from './reducer';
import applyMiddlewares from './middlewares';
import * as actions from './actions';
import * as selectors from './selectors';

/**
* Module Constants
*/
const STORAGE_KEY = `WP_EDIT_POST_PREFERENCES_${ window.userSettings.uid }`;

const store = registerStore( 'core/edit-post', {
reducer: withRehydration( reducer, 'preferences', STORAGE_KEY ),
reducer: restrictPersistence( reducer, 'preferences' ),
actions,
selectors,
persist: true,
} );

applyMiddlewares( store );
loadAndPersist( store, reducer, 'preferences', STORAGE_KEY );
store.dispatch( { type: 'INIT' } );

export default store;
22 changes: 9 additions & 13 deletions editor/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ import { forOwn } from 'lodash';
* WordPress Dependencies
*/
import {
registerReducer,
registerSelectors,
registerActions,
withRehydration,
loadAndPersist,
registerStore,
restrictPersistence,
} from '@wordpress/data';

/**
Expand All @@ -27,16 +24,15 @@ import { validateTokenSettings } from '../components/rich-text/tokens';
/**
* Module Constants
*/
const STORAGE_KEY = `GUTENBERG_PREFERENCES_${ window.userSettings.uid }`;
const MODULE_KEY = 'core/editor';

const store = applyMiddlewares(
registerReducer( MODULE_KEY, withRehydration( reducer, 'preferences', STORAGE_KEY ) )
);
loadAndPersist( store, reducer, 'preferences', STORAGE_KEY );

registerSelectors( MODULE_KEY, selectors );
registerActions( MODULE_KEY, actions );
const store = registerStore( MODULE_KEY, {
reducer: restrictPersistence( reducer, 'preferences' ),
selectors,
actions,
persist: true,
} );
applyMiddlewares( store );

forOwn( tokens, ( { name, settings } ) => {
settings = validateTokenSettings( name, settings, store.getState() );
Expand Down
34 changes: 21 additions & 13 deletions editor/utils/with-history/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,19 @@ const withHistory = ( options = {} ) => ( reducer ) => {
past: [],
present: reducer( undefined, {} ),
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
};

let lastAction;
let shouldCreateUndoLevel = false;

const {
resetTypes = [],
shouldOverwriteState = () => false,
} = options;

return ( state = initialState, action ) => {
const { past, present, future } = state;
const { past, present, future, lastAction, shouldCreateUndoLevel } = state;
const previousAction = lastAction;

lastAction = action;

switch ( action.type ) {
case 'UNDO':
// Can't undo if no past.
Expand All @@ -74,6 +71,8 @@ const withHistory = ( options = {} ) => ( reducer ) => {
past: dropRight( past ),
present: last( past ),
future: [ present, ...future ],
lastAction: null,
shouldCreateUndoLevel: false,
};
case 'REDO':
// Can't redo if no future.
Expand All @@ -85,11 +84,16 @@ const withHistory = ( options = {} ) => ( reducer ) => {
past: [ ...past, present ],
present: first( future ),
future: drop( future ),
lastAction: null,
shouldCreateUndoLevel: false,
};

case 'CREATE_UNDO_LEVEL':
shouldCreateUndoLevel = true;
return state;
return {
...state,
lastAction: null,
shouldCreateUndoLevel: true,
};
}

const nextPresent = reducer( present, action );
Expand All @@ -99,6 +103,8 @@ const withHistory = ( options = {} ) => ( reducer ) => {
past: [],
present: nextPresent,
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
};
}

Expand All @@ -108,18 +114,20 @@ const withHistory = ( options = {} ) => ( reducer ) => {

let nextPast = past;

shouldCreateUndoLevel = ! past.length || shouldCreateUndoLevel;

if ( shouldCreateUndoLevel || ! shouldOverwriteState( action, previousAction ) ) {
if (
shouldCreateUndoLevel ||
! past.length ||
! shouldOverwriteState( action, previousAction )
) {
nextPast = [ ...past, present ];
}

shouldCreateUndoLevel = false;

return {
past: nextPast,
present: nextPresent,
future: [],
shouldCreateUndoLevel: false,
lastAction: action,
};
};
};
Expand Down
27 changes: 25 additions & 2 deletions editor/utils/with-history/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,35 @@ describe( 'withHistory', () => {
past: [],
present: 0,
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
} );
} );

it( 'should track history', () => {
const reducer = withHistory()( counter );

let state;
const action = { type: 'INCREMENT' };
state = reducer( undefined, {} );
state = reducer( state, { type: 'INCREMENT' } );
state = reducer( state, action );

expect( state ).toEqual( {
past: [ 0 ],
present: 1,
future: [],
lastAction: action,
shouldCreateUndoLevel: false,
} );

state = reducer( state, { type: 'INCREMENT' } );
state = reducer( state, action );

expect( state ).toEqual( {
past: [ 0, 1 ],
present: 2,
future: [],
lastAction: action,
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -53,6 +60,8 @@ describe( 'withHistory', () => {
past: [],
present: 0,
future: [ 1 ],
lastAction: null,
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -76,6 +85,8 @@ describe( 'withHistory', () => {
past: [ 0 ],
present: 1,
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -98,6 +109,8 @@ describe( 'withHistory', () => {
past: [],
present: 1,
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -113,6 +126,8 @@ describe( 'withHistory', () => {
past: [ 0 ], // Needs at least one history
present: 2,
future: [],
lastAction: { type: 'INCREMENT' },
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -137,6 +152,8 @@ describe( 'withHistory', () => {
past: [ 0 ],
present: 1,
future: [],
lastAction: { type: 'INCREMENT' },
shouldCreateUndoLevel: false,
} );

state = reducer( state, { type: 'INCREMENT' } );
Expand All @@ -145,6 +162,8 @@ describe( 'withHistory', () => {
past: [ 0 ],
present: 2,
future: [],
lastAction: { type: 'INCREMENT' },
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -162,6 +181,8 @@ describe( 'withHistory', () => {
past: [ 0 ],
present: 1,
future: [],
lastAction: null,
shouldCreateUndoLevel: true,
} );

state = reducer( state, { type: 'INCREMENT' } );
Expand All @@ -170,6 +191,8 @@ describe( 'withHistory', () => {
past: [ 0, 1 ],
present: 2,
future: [],
lastAction: { type: 'INCREMENT' },
shouldCreateUndoLevel: false,
} );
} );
} );
91 changes: 91 additions & 0 deletions packages/data/src/deprecated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* External dependencies
*/
import { get } from 'lodash';

/**
* WordPress dependencies
*/
import deprecated from '@wordpress/deprecated';

/**
* Internal dependencies
*/
import { getPersistenceStorage } from './persist';

/**
* Adds the rehydration behavior to redux reducers.
*
* @param {Function} reducer The reducer to enhance.
* @param {string} reducerKey The reducer key to persist.
* @param {string} storageKey The storage key to use.
*
* @return {Function} Enhanced reducer.
*/
export function withRehydration( reducer, reducerKey, storageKey ) {
deprecated( 'wp.data.withRehydration', {
version: '3.6',
plugin: 'Gutenberg',
hint: 'See https://github.com/WordPress/gutenberg/pull/8146 for more details',
} );

// EnhancedReducer with auto-rehydration
const enhancedReducer = ( state, action ) => {
const nextState = reducer( state, action );

if ( action.type === 'REDUX_REHYDRATE' && action.storageKey === storageKey ) {
return {
...nextState,
[ reducerKey ]: action.payload,
};
}

return nextState;
};

return enhancedReducer;
}

/**
* Loads the initial state and persist on changes.
*
* This should be executed after the reducer's registration.
*
* @param {Object} store Store to enhance.
* @param {Function} reducer The reducer function. Used to get default values and to allow custom serialization by the reducers.
* @param {string} reducerKey The reducer key to persist (example: reducerKey.subReducerKey).
* @param {string} storageKey The storage key to use.
*/
export function loadAndPersist( store, reducer, reducerKey, storageKey ) {
deprecated( 'wp.data.loadAndPersist', {
version: '3.6',
plugin: 'Gutenberg',
hint: 'See https://github.com/WordPress/gutenberg/pull/8146 for more details',
} );

// Load initially persisted value
const persistedString = getPersistenceStorage().getItem( storageKey );
if ( persistedString ) {
const persistedState = {
...get( reducer( undefined, { type: '@@gutenberg/init' } ), reducerKey ),
...JSON.parse( persistedString ),
};

store.dispatch( {
type: 'REDUX_REHYDRATE',
payload: persistedState,
storageKey,
} );
}

// Persist updated preferences
let currentStateValue = get( store.getState(), reducerKey );
store.subscribe( () => {
const newStateValue = get( store.getState(), reducerKey );
if ( newStateValue !== currentStateValue ) {
currentStateValue = newStateValue;
const stateToSave = get( reducer( store.getState(), { type: 'SERIALIZE' } ), reducerKey );
getPersistenceStorage().setItem( storageKey, JSON.stringify( stateToSave ) );
}
} );
}
Loading

0 comments on commit f508995

Please sign in to comment.