Skip to content
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

Add higher order reducer for block editing modes while section editing #66919

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/block-editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1669,14 +1669,16 @@ export const setNavigationMode =
*/
export const __unstableSetEditorMode =
( mode ) =>
( { registry } ) => {
( { registry, dispatch } ) => {
registry.dispatch( preferencesStore ).set( 'core', 'editorTool', mode );

if ( mode === 'navigation' ) {
speak( __( 'You are currently in Write mode.' ) );
} else if ( mode === 'edit' ) {
speak( __( 'You are currently in Design mode.' ) );
}

dispatch( { type: 'SET_EDITOR_MODE', mode } );
};

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ function getEnabledClientIdsTreeUnmemoized( state, rootClientId ) {
export const getEnabledClientIdsTree = createRegistrySelector( ( select ) =>
createSelector( getEnabledClientIdsTreeUnmemoized, ( state ) => [
state.blocks.order,
state.defaultBlockEditingMode,
state.sectionBlockEditingModes,
state.blockEditingModes,
state.settings.templateLock,
state.blockListSettings,
Expand Down
253 changes: 251 additions & 2 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,21 @@ import fastDeepEqual from 'fast-deep-equal/es6';
import { pipe } from '@wordpress/compose';
import { combineReducers, select } from '@wordpress/data';
import deprecated from '@wordpress/deprecated';
import { store as blocksStore } from '@wordpress/blocks';
import {
store as blocksStore,
privateApis as blocksPrivateApis,
} from '@wordpress/blocks';
import { store as preferencesStore } from '@wordpress/preferences';

/**
* Internal dependencies
*/
import { PREFERENCES_DEFAULTS, SETTINGS_DEFAULTS } from './defaults';
import { insertAt, moveTo } from './array';
import { sectionRootClientIdKey } from './private-keys';
import { unlock } from '../lock-unlock';

const { isContentBlock } = unlock( blocksPrivateApis );

const identity = ( x ) => x;

Expand Down Expand Up @@ -165,6 +174,244 @@ export function isUpdatingSameBlockAttribute( action, lastAction ) {
);
}

function recurseInnerBlocks( block, callback ) {
block.innerBlocks?.forEach( ( innerBlock ) => {
callback( innerBlock );
recurseInnerBlocks( innerBlock, callback );
} );
}

function isWithinSection( state, clientId ) {
const sectionRootClientId = state.settings?.[ sectionRootClientIdKey ];
const sectionClientIds = state.blocks.order.get( sectionRootClientId );
let parent = state.blocks.parents.get( clientId );
while ( !! parent ) {
if ( sectionClientIds.includes( parent ) ) {
return true;
}
parent = state.blocks.parents.get( parent );
}
}

function getInsertedBlocksEditingModes(
state,
rootClientId,
insertedBlocks,
{ includeContentOnlyChildren = false }
) {
const editingModes = new Map();
const sectionRootClientId = state.settings?.[ sectionRootClientIdKey ];
const topLevelBlocksAreSections = rootClientId === sectionRootClientId;
const topLevelBlocksAreWithinSections =
! topLevelBlocksAreSections && isWithinSection( state, rootClientId );

for ( const block of insertedBlocks ) {
if ( topLevelBlocksAreSections ) {
editingModes.set( block.clientId, 'contentOnly' );
}

if ( includeContentOnlyChildren ) {
if (
topLevelBlocksAreWithinSections &&
isContentBlock( block.name )
) {
editingModes.set( block.clientId, 'contentOnly' );
}

recurseInnerBlocks( block, ( innerBlock ) => {
if ( isContentBlock( innerBlock.name ) ) {
editingModes.set( innerBlock.clientId, 'contentOnly' );
}
} );
}
}

return editingModes;
}

function getSectionBlockEditingModes(
state,
{ includeContentOnlyChildren = false }
) {
const sectionBlockEditingModes = new Map();
const sectionRootClientId = state.settings?.[ sectionRootClientIdKey ];
sectionBlockEditingModes.set( sectionRootClientId, 'contentOnly' );

const sectionClientIds = state.blocks.order.get( sectionRootClientId );
for ( const sectionClientId of sectionClientIds ) {
sectionBlockEditingModes.set( sectionClientId, 'contentOnly' );

if ( includeContentOnlyChildren ) {
const sectionTree = state.blocks.tree.get( sectionClientId );

recurseInnerBlocks( sectionTree, ( block ) => {
if ( isContentBlock( block.name ) ) {
sectionBlockEditingModes.set(
block.clientId,
'contentOnly'
);
}
} );
}
}

return sectionBlockEditingModes;
}

const withDerivedBlockEditingMode = ( reducer ) => ( state, action ) => {
const newState = reducer( state, action );

// An exception is needed
// here to still recompute the block editing modes when the editor mode
// changes since the editor mode isn't stored within the block editor
// state and changing it won't trigger an altered new state.
if ( action.type !== 'SET_EDITOR_MODE' && newState === state ) {
return state;
}

newState.defaultBlockEditingMode =
state?.defaultBlockEditingMode ?? 'default';
newState.sectionBlockEditingModes =
state?.sectionBlockEditingModes ?? new Map();

const isZoomedOut =
newState?.zoomLevel < 100 || newState?.zoomLevel === 'auto-scaled';
const isNavMode =
select( preferencesStore )?.get( 'core', 'editorTool' ) ===
'navigation';

switch ( action.type ) {
case 'INSERT_BLOCKS':
case 'RECEIVE_BLOCKS': {
if ( isZoomedOut || isNavMode ) {
const rootClientId = action.rootClientId;
const insertedBlocks = action.blocks;
const insertions = getInsertedBlocksEditingModes(
newState,
rootClientId,
insertedBlocks,
{ includeContentOnlyChildren: isNavMode }
);
newState.sectionBlockEditingModes = new Map( [
...newState.sectionBlockEditingModes,
...insertions,
] );
}

break;
}
case 'REPLACE_BLOCKS': {
if ( isZoomedOut || isNavMode ) {
const removedClientIds = action.clientIds;

// Remove modes for the removed blocks.
for ( const clientId of removedClientIds ) {
newState.sectionBlockEditingModes.delete( clientId );

// It's important `state` is used here instead of `newState` because
// the blocks will have already been removed from the new state.
const tree = state.blocks.tree.get( clientId );
recurseInnerBlocks( tree, ( childClientId ) =>
newState.sectionBlockEditingModes.delete(
childClientId
)
);
}

const rootClientId = state.blocks.parents.get(
removedClientIds[ 0 ]
);
const insertedBlocks = action.blocks;

// Update modes for the inserted blocks.
const insertions = getInsertedBlocksEditingModes(
newState,
rootClientId,
insertedBlocks,
{ includeContentOnlyChildren: isNavMode }
);
newState.sectionBlockEditingModes = new Map( [
...newState.sectionBlockEditingModes,
...insertions,
] );
}
break;
}
case 'REMOVE_BLOCKS': {
if ( isZoomedOut || isNavMode ) {
// Remove modes for the removed blocks.
for ( const clientId of action.clientIds ) {
newState.sectionBlockEditingModes.delete( clientId );

// It's important `state` is used here instead of `newState` because
// the blocks will have already been removed from the new state.
const tree = state.blocks.tree.get( clientId );
recurseInnerBlocks( tree, ( childClientId ) =>
newState.sectionBlockEditingModes.delete(
childClientId
)
);
}
}
break;
}
case 'MOVE_BLOCKS_TO_POSITION': {
if ( isZoomedOut || isNavMode ) {
const { toRootClientId, clientIds } = action;

for ( const clientId of clientIds ) {
newState.sectionBlockEditingModes.delete( clientId );

// It's important `state` is used here instead of `newState` because
// the blocks will have already been removed from the new state.
const tree = state.blocks.tree.get( clientId );
recurseInnerBlocks( tree, ( childClientId ) =>
newState.sectionBlockEditingModes.delete(
childClientId
)
);
}

const insertedBlocks = clientIds.map( ( clientId ) =>
newState.blocks.tree.get( clientId )
);
const insertions = getInsertedBlocksEditingModes(
newState,
toRootClientId,
insertedBlocks,
{ includeContentOnlyChildren: isNavMode }
);
newState.sectionBlockEditingModes = new Map( [
...newState.sectionBlockEditingModes,
...insertions,
] );
}

break;
}
case 'SET_EDITOR_MODE':
case 'RESET_ZOOM_LEVEL':
case 'SET_ZOOM_LEVEL': {
if ( isZoomedOut || isNavMode ) {
// When there are sections, the majority of blocks are disabled,
// so the default is set to disabled, which avoids the need to iterate
// every through blocks that are adjacent to or ancestors of the section root.
newState.defaultBlockEditingMode = 'disabled';
newState.sectionBlockEditingModes = getSectionBlockEditingModes(
newState,
{ includeContentOnlyChildren: isNavMode }
);
} else {
newState.defaultBlockEditingMode = 'default';
newState.sectionBlockEditingModes = new Map();
}
break;
}
}

return newState;
};

function updateBlockTreeForBlocks( state, blocks ) {
const treeToUpdate = state.tree;
const stack = [ ...blocks ];
Expand Down Expand Up @@ -2184,4 +2431,6 @@ function withAutomaticChangeReset( reducer ) {
};
}

export default withAutomaticChangeReset( combinedReducers );
export default withDerivedBlockEditingMode(
withAutomaticChangeReset( combinedReducers )
);
Loading
Loading