From 49cb5dbdb1a09a8e9287541dfc08d38b40b99e10 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Sat, 24 Nov 2018 13:04:40 +0100 Subject: [PATCH] Extract the attributes from the editor's state shape to enhance performance --- packages/editor/src/store/reducer.js | 118 +++++++++++++++++++++---- packages/editor/src/store/selectors.js | 13 +-- 2 files changed, 107 insertions(+), 24 deletions(-) diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index d241bb008f45a1..d892e37e5e94c3 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -106,12 +106,38 @@ function getFlattenedBlocks( blocks ) { stack.push( ...innerBlocks ); - flattenedBlocks[ block.clientId ] = block; + flattenedBlocks[ block.clientId ] = omit( block, [ 'attributes' ] ); } return flattenedBlocks; } +/** + * Given an array of blocks, returns an object containing all block attributes, recursing + * into inner blocks. Keys correspond to the block client ID, the value of + * which is the block attributes object. + * + * @param {Array} blocks Blocks to flatten. + * + * @return {Object} Flattened block attributes object. + */ +function getFlattenedBlockAttributes( blocks ) { + const flattenedBlockAttributes = {}; + + const stack = [ ...blocks ]; + while ( stack.length ) { + // `innerBlocks` is redundant data which can fall out of sync, since + // this is reflected in `blocks.order`, so exclude from appended block. + const { innerBlocks, ...block } = stack.shift(); + + stack.push( ...innerBlocks ); + + flattenedBlockAttributes[ block.clientId ] = block.attributes || {}; + } + + return flattenedBlockAttributes; +} + /** * Given a block order map object, returns *all* of the block client IDs that are * a descendant of the given root client ID. @@ -263,6 +289,10 @@ const withBlockReset = ( reducer ) => ( state, action ) => { ...omit( state.byClientId, visibleClientIds ), ...getFlattenedBlocks( action.blocks ), }, + attributesByClientId: { + ...omit( state.attributesByClientId, visibleClientIds ), + ...getFlattenedBlockAttributes( action.blocks ), + }, order: { ...omit( state.order, visibleClientIds ), ...mapBlockOrder( action.blocks ), @@ -369,6 +399,60 @@ export const editor = flow( [ ...getFlattenedBlocks( action.blocks ), }; + // TODO: Check if this action is necessary + case 'UPDATE_BLOCK': + // Ignore updates if block isn't known + if ( ! state[ action.clientId ] ) { + return state; + } + + const changes = omit( action.updates, 'attributes' ); + if ( keys( changes ).length === 0 ) { + return state; + } + + return { + ...state, + [ action.clientId ]: { + ...state[ action.clientId ], + ...omit( action.updates, 'attributes' ), + }, + }; + + case 'INSERT_BLOCKS': + return { + ...state, + ...getFlattenedBlocks( action.blocks ), + }; + + case 'REPLACE_BLOCKS': + if ( ! action.blocks ) { + return state; + } + + return { + ...omit( state, action.clientIds ), + ...getFlattenedBlocks( action.blocks ), + }; + + case 'REMOVE_BLOCKS': + return omit( state, action.clientIds ); + } + + return state; + }, + + attributesByClientId( state = {}, action ) { + switch ( action.type ) { + case 'SETUP_EDITOR_STATE': + return getFlattenedBlockAttributes( action.blocks ); + + case 'RECEIVE_BLOCKS': + return { + ...state, + ...getFlattenedBlockAttributes( action.blocks ), + }; + case 'UPDATE_BLOCK_ATTRIBUTES': // Ignore updates if block isn't known if ( ! state[ action.clientId ] ) { @@ -383,26 +467,24 @@ export const editor = flow( [ } return result; - }, state[ action.clientId ].attributes ); + }, state[ action.clientId ] ); // Skip update if nothing has been changed. The reference will // match the original block if `reduce` had no changed values. - if ( nextAttributes === state[ action.clientId ].attributes ) { + if ( nextAttributes === state[ action.clientId ] ) { return state; } // Otherwise merge attributes into state return { ...state, - [ action.clientId ]: { - ...state[ action.clientId ], - attributes: nextAttributes, - }, + [ action.clientId ]: nextAttributes, }; + // TODO: Check if this action is necessary case 'UPDATE_BLOCK': // Ignore updates if block isn't known - if ( ! state[ action.clientId ] ) { + if ( ! state[ action.clientId ] || ! action.updates.attributes ) { return state; } @@ -410,14 +492,14 @@ export const editor = flow( [ ...state, [ action.clientId ]: { ...state[ action.clientId ], - ...action.updates, + ...action.updates.attributes, }, }; case 'INSERT_BLOCKS': return { ...state, - ...getFlattenedBlocks( action.blocks ), + ...getFlattenedBlockAttributes( action.blocks ), }; case 'REPLACE_BLOCKS': @@ -427,7 +509,7 @@ export const editor = flow( [ return { ...omit( state, action.clientIds ), - ...getFlattenedBlocks( action.blocks ), + ...getFlattenedBlockAttributes( action.blocks ), }; case 'REMOVE_BLOCKS': @@ -441,18 +523,16 @@ export const editor = flow( [ return state; } - return mapValues( state, ( block ) => { - if ( block.name === 'core/block' && block.attributes.ref === id ) { + // TODO fix this by moving this to a higher level (we need the block name) + return mapValues( state, ( attributes ) => { + if ( attributes.ref && attributes.ref === id ) { return { - ...block, - attributes: { - ...block.attributes, - ref: updatedId, - }, + ...attributes, + ref: updatedId, }; } - return block; + return attributes; } ); } } diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index ddbc7770a3c8e1..620493b013405b 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -629,8 +629,7 @@ export const getBlock = createSelector( if ( ! block ) { return null; } - - let { attributes } = block; + let attributes = state.editor.present.blocks.attributesByClientId[ clientId ]; // Inject custom source attribute values. // @@ -659,6 +658,7 @@ export const getBlock = createSelector( }, ( state, clientId ) => [ state.editor.present.blocks.byClientId[ clientId ], + state.editor.present.blocks.attributesByClientId[ clientId ], getBlockDependantsCacheBust( state, clientId ), state.editor.present.edits.meta, state.initialEdits.meta, @@ -1090,6 +1090,7 @@ export const getMultiSelectedBlocks = createSelector( state.editor.present.blocks.order, state.blockSelection.start, state.blockSelection.end, + state.editor.present.blocks.attributesByClientId, state.editor.present.blocks.byClientId, state.editor.present.edits.meta, state.initialEdits.meta, @@ -1140,7 +1141,7 @@ const isAncestorOf = createSelector( return possibleAncestorId === idToCheck; }, ( state ) => [ - state.editor.present.blocks, + state.editor.present.blocks.order, ], ); @@ -1947,7 +1948,8 @@ export const getInserterItems = createSelector( }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.editor.present.blocks, + state.editor.present.blocks.byClientId, + state.editor.present.blocks.order, state.preferences.insertUsage, state.settings.allowedBlockTypes, state.settings.templateLock, @@ -1981,7 +1983,8 @@ export const hasInserterItems = createSelector( }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.editor.present.blocks, + state.editor.present.blocks.order, + state.editor.present.blocks.byClientId, state.settings.allowedBlockTypes, state.settings.templateLock, state.reusableBlocks.data,