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: Absorb block UI on parent mechanism. #42684

Closed
wants to merge 1 commit 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: 2 additions & 2 deletions packages/block-editor/src/components/block-card/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import deprecated from '@wordpress/deprecated';
*/
import BlockIcon from '../block-icon';

function BlockCard( { title, icon, description, blockType } ) {
function BlockCard( { title, icon, description, blockType, backButton } ) {
if ( blockType ) {
deprecated( '`blockType` property in `BlockCard component`', {
since: '5.7',
Expand All @@ -18,7 +18,7 @@ function BlockCard( { title, icon, description, blockType } ) {
}
return (
<div className="block-editor-block-card">
<BlockIcon icon={ icon } showColors />
{ backButton ? backButton : <BlockIcon icon={ icon } showColors /> }
<div className="block-editor-block-card__content">
<h2 className="block-editor-block-card__title">{ title }</h2>
<span className="block-editor-block-card__description">
Expand Down
303 changes: 264 additions & 39 deletions packages/block-editor/src/components/block-inspector/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { __, isRTL } from '@wordpress/i18n';
import {
getBlockType,
getUnregisteredTypeHandlerName,
Expand All @@ -11,8 +11,18 @@ import {
import {
PanelBody,
__experimentalUseSlot as useSlot,
__experimentalNavigatorProvider as NavigatorProvider,
__experimentalNavigatorScreen as NavigatorScreen,
__experimentalNavigatorButton as NavigatorButton,
__experimentalNavigatorBackButton as NavigatorBackButton,
FlexItem,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
__experimentalUseNavigator as useNavigator,
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import { useMemo, useCallback, useEffect, useRef } from '@wordpress/element';
import { chevronRight, chevronLeft } from '@wordpress/icons';

/**
* Internal dependencies
Expand All @@ -29,38 +39,247 @@ import DefaultStylePicker from '../default-style-picker';
import BlockVariationTransforms from '../block-variation-transforms';
import useBlockDisplayInformation from '../use-block-display-information';
import { store as blockEditorStore } from '../../store';
import BlockIcon from '../block-icon';

function useAbsorvingUI( clientId ) {
return useSelect( ( select ) => {
const { getBlockParents, getBlockAttributes } =
select( blockEditorStore );
if ( getBlockAttributes( clientId ).absorveDescendentUI ) {
return clientId;
}
const parentIds = getBlockParents( clientId, true );
for ( const parentId of parentIds ) {
if ( getBlockAttributes( parentId ).absorveDescendentUI ) {
return parentId;
}
}
} );
}

function useContentBlocks( blockTypes, block ) {
const contenBlocksObjectAux = useMemo( () => {
return blockTypes.reduce( ( result, blockType ) => {
if (
Object.entries( blockType.attributes ).some(
( [ , { __experimentalRole } ] ) =>
__experimentalRole === 'content'
)
) {
result[ blockType.name ] = true;
}
return result;
}, {} );
}, [ blockTypes ] );
const isContentBlock = useCallback(
( blockName ) => {
return !! contenBlocksObjectAux[ blockName ];
},
[ blockTypes ]
);
return useMemo( () => {
return getContentBlocks( [ block ], isContentBlock );
}, [ block, isContentBlock ] );
}

function getContentBlocks( blocks, isContentBlock ) {
const result = [];
for ( const block of blocks ) {
if ( isContentBlock( block.name ) ) {
result.push( block );
}
result.push( ...getContentBlocks( block.innerBlocks, isContentBlock ) );
}
return result;
}

function BlockInspectorNavigationEffects( { children } ) {
const { selectBlock } = useDispatch( blockEditorStore );
const { goTo, location } = useNavigator();
const lastLocationClientId = useRef();
const updatingSelectionTo = useRef();
const locationClientId = useMemo( () => {
if ( location.path.startsWith( '/block/' ) ) {
return location.path.substring( '/block/'.length );
}
}, [ location.path ] );
const selectedBlockId = useSelect( ( select ) => {
return select( blockEditorStore ).getSelectedBlockClientId();
} );
// When the location changes update the selection to match the new location.
useEffect( () => {
lastLocationClientId.current = locationClientId;
if ( locationClientId && selectedBlockId !== locationClientId ) {
updatingSelectionTo.current = locationClientId;
selectBlock( locationClientId );
}
}, [ locationClientId ] );
// When the selection changes update the location to root if no selection update to match location is in progress.
useEffect( () => {
if ( updatingSelectionTo.current ) {
updatingSelectionTo.current = undefined;
} else if ( lastLocationClientId.current ) {
goTo( '/' );
}
}, [ selectedBlockId ] );

return children;
}

function BlockNavigationButton( { blockTypes, block, selectedBlock } ) {
const blockType = blockTypes.find( ( { name } ) => name === block.name );
return (
<NavigatorButton
path={ `/block/${ block.clientId }` }
style={
selectedBlock.clientId === block.clientId
? { color: 'var(--wp-admin-theme-color)' }
: {}
}
>
<HStack justify="flex-start">
<BlockIcon icon={ blockType.icon } />
<FlexItem>{ blockType.title }</FlexItem>
</HStack>
</NavigatorButton>
);
}

function BlockNavigatorScreen( { block } ) {
return (
<NavigatorScreen path={ `/block/${ block.clientId }` }>
<BlockInspectorSingleBlock
clientId={ block.clientId }
blockName={ block.name }
backButton={
<NavigatorBackButton
style={
// TODO: This style override is also used in ToolsPanelHeader.
// It should be supported out-of-the-box by Button.
{ minWidth: 24, padding: 0 }
}
icon={ isRTL() ? chevronRight : chevronLeft }
isSmall
aria-label={ __( 'Navigate to the previous view' ) }
/>
}
/>
</NavigatorScreen>
);
}

function BlockInspectorAbsorvedBy( { absorvedBy } ) {
const { blockTypes, block, selectedBlock } = useSelect(
( select ) => {
return {
blockTypes: select( blocksStore ).getBlockTypes(),
block: select( blockEditorStore ).getBlock( absorvedBy ),
selectedBlock: select( blockEditorStore ).getSelectedBlock(),
};
},
[ absorvedBy ]
);
const blockInformation = useBlockDisplayInformation( absorvedBy );
const contentBlocks = useContentBlocks( blockTypes, block );
const showSelectedBlock =
absorvedBy !== selectedBlock.clientId &&
! contentBlocks.some(
( contentBlock ) => contentBlock.clientId === selectedBlock.clientId
);
return (
<div className="block-editor-block-inspector">
<NavigatorProvider initialPath="/">
<BlockInspectorNavigationEffects>
<NavigatorScreen path="/">
<BlockCard { ...blockInformation } />
<BlockVariationTransforms
blockClientId={ absorvedBy }
/>
<VStack
spacing={ 1 }
padding={ 4 }
className="block-editor-block-inspector__block-buttons-container"
>
<h2 className="block-editor-block-card__title">
{ __( 'Parent' ) }
</h2>
<BlockNavigationButton
selectedBlock={ selectedBlock }
block={ block }
blockTypes={ blockTypes }
/>
<h2 className="block-editor-block-card__title">
{ __( 'Content' ) }
</h2>
{ contentBlocks.map( ( contentBlock ) => (
<BlockNavigationButton
selectedBlock={ selectedBlock }
key={ contentBlock.clientId }
block={ contentBlock }
blockTypes={ blockTypes }
/>
) ) }
{ showSelectedBlock && (
<>
<h2 className="block-editor-block-card__title">
{ __( 'Selected block' ) }
</h2>
<BlockNavigationButton
selectedBlock={ selectedBlock }
block={ selectedBlock }
blockTypes={ blockTypes }
/>
</>
) }
;
</VStack>
</NavigatorScreen>
<BlockNavigatorScreen block={ block } />
{ contentBlocks.map( ( contentBlock ) => {
return (
<BlockNavigatorScreen
key={ contentBlock.clientId }
block={ contentBlock }
/>
);
} ) }
{ showSelectedBlock && (
<BlockNavigatorScreen block={ selectedBlock } />
) }
</BlockInspectorNavigationEffects>
</NavigatorProvider>
</div>
);
}

const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => {
const {
count,
hasBlockStyles,
selectedBlockName,
selectedBlockClientId,
blockType,
} = useSelect( ( select ) => {
const {
getSelectedBlockClientId,
getSelectedBlockCount,
getBlockName,
} = select( blockEditorStore );
const { getBlockStyles } = select( blocksStore );

const _selectedBlockClientId = getSelectedBlockClientId();
const _selectedBlockName =
_selectedBlockClientId && getBlockName( _selectedBlockClientId );
const _blockType =
_selectedBlockName && getBlockType( _selectedBlockName );
const blockStyles =
_selectedBlockName && getBlockStyles( _selectedBlockName );

return {
count: getSelectedBlockCount(),
selectedBlockClientId: _selectedBlockClientId,
selectedBlockName: _selectedBlockName,
blockType: _blockType,
hasBlockStyles: blockStyles && blockStyles.length > 0,
};
}, [] );
const { count, selectedBlockName, selectedBlockClientId, blockType } =
useSelect( ( select ) => {
const {
getSelectedBlockClientId,
getSelectedBlockCount,
getBlockName,
} = select( blockEditorStore );
const { getBlockStyles } = select( blocksStore );

const _selectedBlockClientId = getSelectedBlockClientId();
const _selectedBlockName =
_selectedBlockClientId &&
getBlockName( _selectedBlockClientId );
const _blockType =
_selectedBlockName && getBlockType( _selectedBlockName );
const blockStyles =
_selectedBlockName && getBlockStyles( _selectedBlockName );

return {
count: getSelectedBlockCount(),
selectedBlockClientId: _selectedBlockClientId,
selectedBlockName: _selectedBlockName,
blockType: _blockType,
hasBlockStyles: blockStyles && blockStyles.length > 0,
};
}, [] );
const absorvedBy = useAbsorvingUI( selectedBlockClientId );

if ( count > 1 ) {
return (
Expand Down Expand Up @@ -109,24 +328,30 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => {
}
return null;
}
if ( absorvedBy ) {
return <BlockInspectorAbsorvedBy absorvedBy={ absorvedBy } />;
}
return (
<BlockInspectorSingleBlock
clientId={ selectedBlockClientId }
blockName={ blockType.name }
hasBlockStyles={ hasBlockStyles }
/>
);
};

const BlockInspectorSingleBlock = ( {
clientId,
blockName,
hasBlockStyles,
} ) => {
const BlockInspectorSingleBlock = ( { clientId, blockName, backButton } ) => {
const hasBlockStyles = useSelect(
( select ) => {
const { getBlockStyles } = select( blocksStore );
const blockStyles = getBlockStyles( blockName );
return blockStyles && blockStyles.length > 0;
},
[ blockName ]
);
const blockInformation = useBlockDisplayInformation( clientId );
return (
<div className="block-editor-block-inspector">
<BlockCard { ...blockInformation } />
<BlockCard backButton={ backButton } { ...blockInformation } />
<BlockVariationTransforms blockClientId={ clientId } />
{ hasBlockStyles && (
<div>
Expand Down
14 changes: 14 additions & 0 deletions packages/block-editor/src/components/block-inspector/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,17 @@
padding: ($grid-unit-20 * 2) $grid-unit-20;
text-align: center;
}


.block-editor-block-inspector__block-buttons-container {
border-top: $border-width solid $gray-200;
padding: $grid-unit-20;
}

.block-editor-block-inspector__block-type-type {
font-weight: 500;
&.block-editor-block-inspector__block-type-type {
line-height: $button-size-small;
margin: 0 0 $grid-unit-05;
}
}
Loading