From a247fe83dcc29ec316a231f67404be77f61a5ad7 Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:39:06 +0200 Subject: [PATCH] Auto-inserting blocks: Remove toggle if block is present elsewhere (#54024) If a block that would normally be auto-inserted is not found in its designated position but elsewhere, we can assume somewhat safely that it was inserted manually, and that the user is aware of the block and won't need any UI elements to make it more visible. Additionally, this PR fixes most of the following warnings (raised [here](https://github.com/WordPress/gutenberg/pull/52969#discussion_r1308713275)): ``` The 'useSelect' hook returns different values when called with the same state and parameters. This can lead to unnecessary rerenders. ``` --------- Co-authored-by: George Mamadashvili --- .../src/hooks/auto-inserting-blocks.js | 141 +++++++++++------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/packages/block-editor/src/hooks/auto-inserting-blocks.js b/packages/block-editor/src/hooks/auto-inserting-blocks.js index 308212893e7cda..9d5b9add0770c6 100644 --- a/packages/block-editor/src/hooks/auto-inserting-blocks.js +++ b/packages/block-editor/src/hooks/auto-inserting-blocks.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { addFilter } from '@wordpress/hooks'; -import { Fragment } from '@wordpress/element'; +import { Fragment, useMemo } from '@wordpress/element'; import { __experimentalHStack as HStack, PanelBody, @@ -19,54 +19,53 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { BlockIcon, InspectorControls } from '../components'; import { store as blockEditorStore } from '../store'; -function AutoInsertingBlocksControl( props ) { - const { autoInsertedBlocksForCurrentBlock, groupedAutoInsertedBlocks } = - useSelect( - ( select ) => { - const { getBlockTypes } = select( blocksStore ); - const _autoInsertedBlocksForCurrentBlock = - getBlockTypes()?.filter( - ( { autoInsert } ) => - autoInsert && props.blockName in autoInsert - ); +const EMPTY_OBJECT = {}; - // Group by block namespace (i.e. prefix before the slash). - const _groupedAutoInsertedBlocks = - _autoInsertedBlocksForCurrentBlock?.reduce( - ( groups, block ) => { - const [ namespace ] = block.name.split( '/' ); - if ( ! groups[ namespace ] ) { - groups[ namespace ] = []; - } - groups[ namespace ].push( block ); - return groups; - }, - {} - ); +function AutoInsertingBlocksControl( props ) { + const blockTypes = useSelect( + ( select ) => select( blocksStore ).getBlockTypes(), + [] + ); - return { - autoInsertedBlocksForCurrentBlock: - _autoInsertedBlocksForCurrentBlock, - groupedAutoInsertedBlocks: _groupedAutoInsertedBlocks, - }; - }, - [ props.blockName ] - ); + const autoInsertedBlocksForCurrentBlock = useMemo( + () => + blockTypes?.filter( + ( { autoInsert } ) => + autoInsert && props.blockName in autoInsert + ), + [ blockTypes, props.blockName ] + ); - const { - autoInsertedBlockClientIds, - blockIndex, - rootClientId, - innerBlocksLength, - } = useSelect( + const { blockIndex, rootClientId, innerBlocksLength } = useSelect( ( select ) => { const { getBlock, getBlockIndex, getBlockRootClientId } = select( blockEditorStore ); - const _rootClientId = getBlockRootClientId( props.clientId ); + + return { + blockIndex: getBlockIndex( props.clientId ), + innerBlocksLength: getBlock( props.clientId )?.innerBlocks + ?.length, + rootClientId: getBlockRootClientId( props.clientId ), + }; + }, + [ props.clientId ] + ); + + const autoInsertedBlockClientIds = useSelect( + ( select ) => { + const { getBlock, getGlobalBlockCount } = + select( blockEditorStore ); const _autoInsertedBlockClientIds = autoInsertedBlocksForCurrentBlock.reduce( ( clientIds, block ) => { + // If the block doesn't exist anywhere in the block tree, + // we know that we have to display the toggle for it, and set + // it to disabled. + if ( getGlobalBlockCount( block.name ) === 0 ) { + return clientIds; + } + const relativePosition = block?.autoInsert?.[ props.blockName ]; let candidates; @@ -78,7 +77,7 @@ function AutoInsertingBlocksControl( props ) { // as an auto-inserted block (inserted `before` or `after` the current one), // as the block might've been auto-inserted and then moved around a bit by the user. candidates = - getBlock( _rootClientId )?.innerBlocks; + getBlock( rootClientId )?.innerBlocks; break; case 'first_child': @@ -96,38 +95,66 @@ function AutoInsertingBlocksControl( props ) { ( { name } ) => name === block.name ); + // If the block exists in the designated location, we consider it auto-inserted + // and show the toggle as enabled. if ( autoInsertedBlock ) { - clientIds[ block.name ] = - autoInsertedBlock.clientId; + return { + ...clientIds, + [ block.name ]: autoInsertedBlock.clientId, + }; } - // TOOD: If no auto-inserted block was found in any of its designated locations, - // we want to check if it's present elsewhere in the block tree. - // If it is, we'd consider it manually inserted and would want to remove the - // corresponding toggle from the block inspector panel. - - return clientIds; + // If no auto-inserted block was found in any of its designated locations, + // but it exists elsewhere in the block tree, we consider it manually inserted. + // In this case, we take note and will remove the corresponding toggle from the + // block inspector panel. + return { + ...clientIds, + [ block.name ]: false, + }; }, {} ); - return { - blockIndex: getBlockIndex( props.clientId ), - innerBlocksLength: getBlock( props.clientId )?.innerBlocks - ?.length, - rootClientId: _rootClientId, - autoInsertedBlockClientIds: _autoInsertedBlockClientIds, - }; + if ( Object.values( _autoInsertedBlockClientIds ).length > 0 ) { + return _autoInsertedBlockClientIds; + } + + return EMPTY_OBJECT; }, - [ autoInsertedBlocksForCurrentBlock, props.blockName, props.clientId ] + [ + autoInsertedBlocksForCurrentBlock, + props.blockName, + props.clientId, + rootClientId, + ] ); const { insertBlock, removeBlock } = useDispatch( blockEditorStore ); - if ( ! autoInsertedBlocksForCurrentBlock.length ) { + // Remove toggle if block isn't present in the designated location but elsewhere in the block tree. + const autoInsertedBlocksForCurrentBlockIfNotPresentElsewhere = + autoInsertedBlocksForCurrentBlock?.filter( + ( block ) => autoInsertedBlockClientIds?.[ block.name ] !== false + ); + + if ( ! autoInsertedBlocksForCurrentBlockIfNotPresentElsewhere.length ) { return null; } + // Group by block namespace (i.e. prefix before the slash). + const groupedAutoInsertedBlocks = autoInsertedBlocksForCurrentBlock.reduce( + ( groups, block ) => { + const [ namespace ] = block.name.split( '/' ); + if ( ! groups[ namespace ] ) { + groups[ namespace ] = []; + } + groups[ namespace ].push( block ); + return groups; + }, + {} + ); + const insertBlockIntoDesignatedLocation = ( block, relativePosition ) => { switch ( relativePosition ) { case 'before':