Skip to content

Commit

Permalink
Add higher order reducer for pattern block editing modes.
Browse files Browse the repository at this point in the history
- Remove the prev. React effect for managing pattern block editing modes
- Implement higher order reducer in the block editor store ... it:
    - Tracks the clientIds of pattern blocks.
    - Uses the pattern block clientIds to manage block editing modes
      for pattern blocks and their inner blocks.
    - Updates both on any actions that change block lists.
  • Loading branch information
talldan committed Nov 13, 2024
1 parent dacc429 commit 4c7653a
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 52 deletions.
168 changes: 167 additions & 1 deletion packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2131,6 +2131,170 @@ const combinedReducers = combineReducers( {
zoomLevel,
} );

function recurseBlocks( _blocks, callback ) {
if ( ! _blocks?.length ) {
return;
}

for ( const block of _blocks ) {
callback( block );
recurseBlocks( block?.innerBlocks, callback );
}
}

function hasParentWithName( state, clientId, name ) {
let parent = state.blocks.parents.get( clientId );
while ( parent ) {
if ( state.blocks.byClientId.get( parent ).name === name ) {
return true;
}
parent = state.blocks.parents.get( parent );
}
return false;
}

function getPatternBlockEditingModes( state ) {
if ( ! state.patternClientIds?.size ) {
return new Map();
}

const patternBlockEditingModes = new Map();
for ( const clientId of state.patternClientIds ) {
// The pattern block is a controlled block, so the inner blocks are stored
// under a special key in the tree.
// Fallback to the normal key if the special key doesn't exist, as it will
// still allow adding the editing mode of the pattern block itself.
const patternTree =
state.blocks.tree.get( `controlled||${ clientId }` ) ??
state.blocks.tree.get( clientId );

if ( hasParentWithName( state, clientId, 'core/block' ) ) {
// This is a nested pattern block, it should be set to disabled,
// along with all its child blocks.
patternBlockEditingModes.set( clientId, 'disabled' );
recurseBlocks( patternTree?.innerBlocks, ( block ) => {
patternBlockEditingModes.set( block.clientId, 'disabled' );
} );
} else {
// Set the parent pattern block to contentOnly.
patternBlockEditingModes.set( clientId, 'contentOnly' );
recurseBlocks( patternTree?.innerBlocks, ( block ) => {
// If an inner block has bindings, it should be set to contentOnly.
// Else it should be set to disabled.
if (
block?.attributes?.metadata?.bindings &&
Object.keys( block?.attributes?.metadata?.bindings ).length
) {
patternBlockEditingModes.set(
block.clientId,
'contentOnly'
);
} else {
patternBlockEditingModes.set( block.clientId, 'disabled' );
}
} );
}
}
return patternBlockEditingModes;
}

const withPatternBlockEditingModes = ( reducer ) => {
return ( state, action ) => {
const nextState = reducer( state, action );

if ( nextState === state ) {
return state;
}

nextState.patternClientIds = state?.patternClientIds ?? new Set();
nextState.patternBlockEditingModes =
state?.patternBlockEditingModes ?? new Map();

switch ( action.type ) {
case 'RESET_BLOCKS': {
nextState.patternClientIds = new Set();
recurseBlocks( action.blocks, ( block ) => {
if ( block.name === 'core/block' ) {
nextState.patternClientIds.add( block.clientId );
}
} );
nextState.patternBlockEditingModes =
getPatternBlockEditingModes( nextState );
break;
}
case 'INSERT_BLOCKS': {
recurseBlocks( action.blocks, ( block ) => {
if ( block.name === 'core/block' ) {
nextState.patternClientIds.add( block.clientId );
}
} );
nextState.patternBlockEditingModes =
getPatternBlockEditingModes( nextState );
break;
}
case 'REMOVE_BLOCKS': {
for ( const removedClientId of action.clientIds ) {
const removedTree =
state.blocks.tree.get(
`controlled||${ removedClientId }`
) ?? state.blocks.tree.get( removedClientId );
if ( removedTree ) {
recurseBlocks( [ removedTree ], ( block ) => {
nextState.patternClientIds.delete( block.clientId );
} );
}
}
nextState.patternBlockEditingModes =
getPatternBlockEditingModes( nextState );
break;
}
case 'REPLACE_INNER_BLOCKS': {
const rootTree =
state.blocks.tree.get(
`controlled||${ action.rootClientId }`
) ?? state.blocks.tree.get( action.rootClientId );

if ( rootTree ) {
recurseBlocks( rootTree?.innerBlocks, ( block ) => {
nextState.patternClientIds.delete( block.clientId );
} );
}
recurseBlocks( action.blocks, ( block ) => {
if ( block.name === 'core/block' ) {
nextState.patternClientIds.add( block.clientId );
}
} );
nextState.patternBlockEditingModes =
getPatternBlockEditingModes( nextState );
break;
}
case 'REPLACE_BLOCKS': {
for ( const removedClientId of action.clientIds ) {
const removedTree =
state.blocks.tree.get(
`controlled||${ removedClientId }`
) ?? state.blocks.tree.get( removedClientId );
if ( removedTree ) {
recurseBlocks( [ removedTree ], ( block ) => {
nextState.patternClientIds.delete( block.clientId );
} );
}
}
recurseBlocks( action.blocks, ( block ) => {
if ( block.name === 'core/block' ) {
nextState.patternClientIds.add( block.clientId );
}
} );
nextState.patternBlockEditingModes =
getPatternBlockEditingModes( nextState );
break;
}
}

return nextState;
};
};

function withAutomaticChangeReset( reducer ) {
return ( state, action ) => {
const nextState = reducer( state, action );
Expand Down Expand Up @@ -2184,4 +2348,6 @@ function withAutomaticChangeReset( reducer ) {
};
}

export default withAutomaticChangeReset( combinedReducers );
export default withPatternBlockEditingModes(
withAutomaticChangeReset( combinedReducers )
);
4 changes: 4 additions & 0 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3071,6 +3071,10 @@ export const getBlockEditingMode = createRegistrySelector(
return 'disabled';
}

if ( state?.patternBlockEditingModes?.has( clientId ) ) {
return state?.patternBlockEditingModes.get( clientId );
}

const editorMode = __unstableGetEditorMode( state );
if ( editorMode === 'navigation' ) {
const sectionRootClientId = getSectionRootClientId( state );
Expand Down
57 changes: 6 additions & 51 deletions packages/block-library/src/block/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import clsx from 'clsx';
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { useRef, useMemo, useEffect } from '@wordpress/element';
import { useRef, useMemo } from '@wordpress/element';
import {
useEntityRecord,
store as coreStore,
Expand Down Expand Up @@ -37,12 +37,10 @@ import { getBlockBindingsSource } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { name as patternBlockName } from './index';
import { unlock } from '../lock-unlock';

const { useLayoutClasses } = unlock( blockEditorPrivateApis );
const { isOverridableBlock, hasOverridableBlocks } =
unlock( patternsPrivateApis );
const { hasOverridableBlocks } = unlock( patternsPrivateApis );

const fullAlignments = [ 'full', 'wide', 'left', 'right' ];

Expand Down Expand Up @@ -75,22 +73,6 @@ const useInferredLayout = ( blocks, parentLayout ) => {
}, [ blocks, parentLayout ] );
};

function setBlockEditMode( setEditMode, blocks, mode ) {
blocks.forEach( ( block ) => {
const editMode =
mode ||
( isOverridableBlock( block ) ? 'contentOnly' : 'disabled' );
setEditMode( block.clientId, editMode );

setBlockEditMode(
setEditMode,
block.innerBlocks,
// Disable editing for nested patterns.
block.name === patternBlockName ? 'disabled' : mode
);
} );
}

function RecursionWarning() {
const blockProps = useBlockProps();
return (
Expand Down Expand Up @@ -171,7 +153,6 @@ function ReusableBlockEdit( {
name,
attributes: { ref, content },
__unstableParentLayout: parentLayout,
clientId: patternClientId,
setAttributes,
} ) {
const { record, hasResolved } = useEntityRecord(
Expand All @@ -184,49 +165,24 @@ function ReusableBlockEdit( {
} );
const isMissing = hasResolved && ! record;

const { setBlockEditingMode, __unstableMarkLastChangeAsPersistent } =
const { __unstableMarkLastChangeAsPersistent } =
useDispatch( blockEditorStore );

const {
innerBlocks,
onNavigateToEntityRecord,
editingMode,
hasPatternOverridesSource,
} = useSelect(
const { onNavigateToEntityRecord, hasPatternOverridesSource } = useSelect(
( select ) => {
const { getBlocks, getSettings, getBlockEditingMode } =
select( blockEditorStore );
const { getSettings } = select( blockEditorStore );
// For editing link to the site editor if the theme and user permissions support it.
return {
innerBlocks: getBlocks( patternClientId ),
onNavigateToEntityRecord:
getSettings().onNavigateToEntityRecord,
editingMode: getBlockEditingMode( patternClientId ),
hasPatternOverridesSource: !! getBlockBindingsSource(
'core/pattern-overrides'
),
};
},
[ patternClientId ]
[]
);

// Sync the editing mode of the pattern block with the inner blocks.
useEffect( () => {
setBlockEditMode(
setBlockEditingMode,
innerBlocks,
// Disable editing if the pattern itself is disabled.
editingMode === 'disabled' || ! hasPatternOverridesSource
? 'disabled'
: undefined
);
}, [
editingMode,
innerBlocks,
setBlockEditingMode,
hasPatternOverridesSource,
] );

const canOverrideBlocks = useMemo(
() => hasPatternOverridesSource && hasOverridableBlocks( blocks ),
[ hasPatternOverridesSource, blocks ]
Expand All @@ -244,7 +200,6 @@ function ReusableBlockEdit( {
} );

const innerBlocksProps = useInnerBlocksProps( blockProps, {
templateLock: 'all',
layout,
value: blocks,
onInput: NOOP,
Expand Down

0 comments on commit 4c7653a

Please sign in to comment.