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

BlockSwitcher: Defer transform calculations until the Dropdown is open #57892

Merged
Merged
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
244 changes: 133 additions & 111 deletions packages/block-editor/src/components/block-switcher/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,75 +29,44 @@ import BlockStylesMenu from './block-styles-menu';
import PatternTransformationsMenu from './pattern-transformations-menu';
import useBlockDisplayTitle from '../block-title/use-block-display-title';

export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => {
function BlockSwitcherDropdownMenuContents( {
onClose,
clientIds,
hasBlockStyles,
canRemove,
} ) {
const { replaceBlocks, multiSelect, updateBlockAttributes } =
useDispatch( blockEditorStore );
const blockInformation = useBlockDisplayInformation( blocks[ 0 ].clientId );
const {
possibleBlockTransformations,
canRemove,
hasBlockStyles,
icon,
patterns,
} = useSelect(
const { possibleBlockTransformations, patterns, blocks } = useSelect(
( select ) => {
const {
getBlocksByClientId,
getBlockRootClientId,
getBlockTransformItems,
__experimentalGetPatternTransformItems,
canRemoveBlocks,
} = select( blockEditorStore );
const { getBlockStyles, getBlockType } = select( blocksStore );
const rootClientId = getBlockRootClientId(
Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds
);
const [ { name: firstBlockName } ] = blocks;
const _isSingleBlockSelected = blocks.length === 1;
const styles =
_isSingleBlockSelected && getBlockStyles( firstBlockName );
let _icon;
if ( _isSingleBlockSelected ) {
_icon = blockInformation?.icon; // Take into account active block variations.
} else {
const isSelectionOfSameType =
new Set( blocks.map( ( { name } ) => name ) ).size === 1;
// When selection consists of blocks of multiple types, display an
// appropriate icon to communicate the non-uniformity.
_icon = isSelectionOfSameType
? getBlockType( firstBlockName )?.icon
: copy;
}
const _blocks = getBlocksByClientId( clientIds );
return {
blocks: _blocks,
possibleBlockTransformations: getBlockTransformItems(
blocks,
_blocks,
rootClientId
),
canRemove: canRemoveBlocks( clientIds, rootClientId ),
hasBlockStyles: !! styles?.length,
icon: _icon,
patterns: __experimentalGetPatternTransformItems(
blocks,
_blocks,
rootClientId
),
};
},
[ clientIds, blocks, blockInformation?.icon ]
[ clientIds ]
);

const blockVariationTransformations = useBlockVariationTransforms( {
clientIds,
blocks,
} );

const blockTitle = useBlockDisplayTitle( {
clientId: Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds,
maximumLength: 35,
} );

const isSingleBlock = blocks.length === 1;
const isReusable = isSingleBlock && isReusableBlock( blocks[ 0 ] );
const isTemplate = isSingleBlock && isTemplatePart( blocks[ 0 ] );

function selectForMultipleBlocks( insertedBlocks ) {
if ( insertedBlocks.length > 1 ) {
multiSelect(
Expand All @@ -106,33 +75,31 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => {
);
}
}

// Simple block tranformation based on the `Block Transforms` API.
function onBlockTransform( name ) {
const newBlocks = switchToBlockType( blocks, name );
replaceBlocks( clientIds, newBlocks );
selectForMultipleBlocks( newBlocks );
}

function onBlockVariationTransform( name ) {
updateBlockAttributes( blocks[ 0 ].clientId, {
...blockVariationTransformations.find(
( { name: variationName } ) => variationName === name
).attributes,
} );
}

// Pattern transformation through the `Patterns` API.
function onPatternTransform( transformedBlocks ) {
replaceBlocks( clientIds, transformedBlocks );
selectForMultipleBlocks( transformedBlocks );
}

/**
* The `isTemplate` check is a stopgap solution here.
* Ideally, the Transforms API should handle this
* by allowing to exclude blocks from wildcard transformations.
*/
const isSingleBlock = blocks.length === 1;
const isTemplate = isSingleBlock && isTemplatePart( blocks[ 0 ] );
const hasPossibleBlockTransformations =
!! possibleBlockTransformations.length && canRemove && ! isTemplate;
const hasPossibleBlockVariationTransformations =
Expand All @@ -141,11 +108,118 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => {
const hasBlockOrBlockVariationTransforms =
hasPossibleBlockTransformations ||
hasPossibleBlockVariationTransformations;
const showDropdown =
const hasContents =
hasBlockStyles ||
hasBlockOrBlockVariationTransforms ||
hasPatternTransformation;
if ( ! showDropdown ) {
if ( ! hasContents ) {
return (
<p className="block-editor-block-switcher__no-transforms">
{ __( 'No transforms.' ) }
</p>
);
}
return (
<div className="block-editor-block-switcher__container">
{ hasPatternTransformation && (
<PatternTransformationsMenu
blocks={ blocks }
patterns={ patterns }
onSelect={ ( transformedBlocks ) => {
onPatternTransform( transformedBlocks );
onClose();
} }
/>
) }
{ hasBlockOrBlockVariationTransforms && (
<BlockTransformationsMenu
className="block-editor-block-switcher__transforms__menugroup"
possibleBlockTransformations={
possibleBlockTransformations
}
possibleBlockVariationTransformations={
blockVariationTransformations
}
blocks={ blocks }
onSelect={ ( name ) => {
onBlockTransform( name );
onClose();
} }
onSelectVariation={ ( name ) => {
onBlockVariationTransform( name );
onClose();
} }
/>
) }
{ hasBlockStyles && (
<BlockStylesMenu
hoveredBlock={ blocks[ 0 ] }
onSwitch={ onClose }
/>
) }
</div>
);
}

export const BlockSwitcher = ( { clientIds } ) => {
const blockInformation = useBlockDisplayInformation( clientIds?.[ 0 ] );
const {
canRemove,
hasBlockStyles,
icon,
invalidBlocks,
isReusable,
isTemplate,
} = useSelect(
( select ) => {
const {
getBlockRootClientId,
getBlocksByClientId,
canRemoveBlocks,
} = select( blockEditorStore );
const { getBlockStyles, getBlockType } = select( blocksStore );
const _blocks = getBlocksByClientId( clientIds );
if ( ! _blocks.length || _blocks.some( ( block ) => ! block ) ) {
return { invalidBlocks: true };
}
const rootClientId = getBlockRootClientId( clientIds );
const [ { name: firstBlockName } ] = _blocks;
const _isSingleBlockSelected = _blocks.length === 1;
let _icon;
if ( _isSingleBlockSelected ) {
_icon = blockInformation?.icon; // Take into account active block variations.
} else {
const isSelectionOfSameType =
new Set( _blocks.map( ( { name } ) => name ) ).size === 1;
// When selection consists of blocks of multiple types, display an
// appropriate icon to communicate the non-uniformity.
_icon = isSelectionOfSameType
? getBlockType( firstBlockName )?.icon
: copy;
}
return {
canRemove: canRemoveBlocks( clientIds, rootClientId ),
hasBlockStyles:
_isSingleBlockSelected &&
!! getBlockStyles( firstBlockName )?.length,
icon: _icon,
isReusable:
_isSingleBlockSelected && isReusableBlock( _blocks[ 0 ] ),
isTemplate:
_isSingleBlockSelected && isTemplatePart( _blocks[ 0 ] ),
};
},
[ clientIds, blockInformation?.icon ]
);
const blockTitle = useBlockDisplayTitle( {
clientId: clientIds?.[ 0 ],
maximumLength: 35,
} );
if ( invalidBlocks ) {
return null;
}
const hideDropdown = ! hasBlockStyles && ! canRemove;
if ( hideDropdown ) {
return (
<ToolbarGroup>
<ToolbarButton
Expand All @@ -166,23 +240,21 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => {
</ToolbarGroup>
);
}

const isSingleBlock = clientIds.length === 1;
const blockSwitcherLabel = isSingleBlock
? blockTitle
: __( 'Multiple blocks selected' );

const blockSwitcherDescription = isSingleBlock
? __( 'Change block type or style' )
: sprintf(
/* translators: %d: number of blocks. */
_n(
'Change type of %d block',
'Change type of %d blocks',
blocks.length
clientIds.length
),
blocks.length
clientIds.length
);

return (
<ToolbarGroup>
<ToolbarItem>
Expand Down Expand Up @@ -215,46 +287,12 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => {
menuProps={ { orientation: 'both' } }
>
{ ( { onClose } ) => (
<div className="block-editor-block-switcher__container">
{ hasPatternTransformation && (
<PatternTransformationsMenu
blocks={ blocks }
patterns={ patterns }
onSelect={ ( transformedBlocks ) => {
onPatternTransform(
transformedBlocks
);
onClose();
} }
/>
) }
{ hasBlockOrBlockVariationTransforms && (
<BlockTransformationsMenu
className="block-editor-block-switcher__transforms__menugroup"
possibleBlockTransformations={
possibleBlockTransformations
}
possibleBlockVariationTransformations={
blockVariationTransformations
}
blocks={ blocks }
onSelect={ ( name ) => {
onBlockTransform( name );
onClose();
} }
onSelectVariation={ ( name ) => {
onBlockVariationTransform( name );
onClose();
} }
/>
) }
{ hasBlockStyles && (
<BlockStylesMenu
hoveredBlock={ blocks[ 0 ] }
onSwitch={ onClose }
/>
) }
</div>
<BlockSwitcherDropdownMenuContents
onClose={ onClose }
clientIds={ clientIds }
hasBlockStyles={ hasBlockStyles }
canRemove={ canRemove }
/>
) }
</DropdownMenu>
) }
Expand All @@ -263,20 +301,4 @@ export const BlockSwitcherDropdownMenu = ( { clientIds, blocks } ) => {
);
};

export const BlockSwitcher = ( { clientIds } ) => {
const blocks = useSelect(
( select ) =>
select( blockEditorStore ).getBlocksByClientId( clientIds ),
[ clientIds ]
);

if ( ! blocks.length || blocks.some( ( block ) => ! block ) ) {
return null;
}

return (
<BlockSwitcherDropdownMenu clientIds={ clientIds } blocks={ blocks } />
);
};

export default BlockSwitcher;
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,9 @@
}
}
}

.block-editor-block-switcher__no-transforms {
color: $gray-700;
padding: 6px $grid-unit;
margin: 0;
}
Loading
Loading