From 26167f549e7bbd2e6bc164631cc41065a06be681 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Fri, 25 Oct 2024 15:15:27 +0300 Subject: [PATCH] Iterate zoom out shuffle into a more visual control (#66194) Co-authored-by: ntsekouras Co-authored-by: richtabor Co-authored-by: youknowriad Co-authored-by: jasmussen Co-authored-by: jameskoster Co-authored-by: ellatrix Co-authored-by: afercia --- .../components/block-toolbar/change-design.js | 133 ++++++++++++++++++ .../src/components/block-toolbar/index.js | 11 +- .../src/components/block-toolbar/shuffle.js | 111 --------------- .../src/components/block-toolbar/style.scss | 16 +++ 4 files changed, 152 insertions(+), 119 deletions(-) create mode 100644 packages/block-editor/src/components/block-toolbar/change-design.js delete mode 100644 packages/block-editor/src/components/block-toolbar/shuffle.js diff --git a/packages/block-editor/src/components/block-toolbar/change-design.js b/packages/block-editor/src/components/block-toolbar/change-design.js new file mode 100644 index 00000000000000..ecfeff6cb1ed3e --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar/change-design.js @@ -0,0 +1,133 @@ +/** + * WordPress dependencies + */ +import { + ToolbarButton, + ToolbarGroup, + Dropdown, + __experimentalDropdownContentWrapper as DropdownContentWrapper, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { cloneBlock } from '@wordpress/blocks'; +import { useMemo } from '@wordpress/element'; +import { useAsyncList } from '@wordpress/compose'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; +import BlockPatternsList from '../block-patterns-list'; + +const EMPTY_ARRAY = []; +const MAX_PATTERNS_TO_SHOW = 6; +const POPOVER_PROPS = { + placement: 'bottom-start', +}; + +export default function ChangeDesign( { clientId } ) { + const { categories, currentPatternName, patterns } = useSelect( + ( select ) => { + const { + getBlockAttributes, + getBlockRootClientId, + __experimentalGetAllowedPatterns, + } = select( blockEditorStore ); + const attributes = getBlockAttributes( clientId ); + const _categories = attributes?.metadata?.categories || EMPTY_ARRAY; + const rootBlock = getBlockRootClientId( clientId ); + + // Calling `__experimentalGetAllowedPatterns` is expensive. + // Checking if the block can be changed prevents unnecessary selector calls. + // See: https://github.com/WordPress/gutenberg/pull/64736. + const _patterns = + _categories.length > 0 + ? __experimentalGetAllowedPatterns( rootBlock ) + : EMPTY_ARRAY; + return { + categories: _categories, + currentPatternName: attributes?.metadata?.patternName, + patterns: _patterns, + }; + }, + [ clientId ] + ); + const { replaceBlocks } = useDispatch( blockEditorStore ); + const sameCategoryPatternsWithSingleWrapper = useMemo( () => { + if ( categories.length === 0 || ! patterns || patterns.length === 0 ) { + return EMPTY_ARRAY; + } + return patterns + .filter( ( pattern ) => { + const isCorePattern = + pattern.source === 'core' || + ( pattern.source?.startsWith( 'pattern-directory' ) && + pattern.source !== 'pattern-directory/theme' ); + return ( + // Check if the pattern has only one top level block, + // otherwise we may switch to a pattern that doesn't have replacement suggestions. + pattern.blocks.length === 1 && + // We exclude the core patterns and pattern directory patterns that are not theme patterns. + ! isCorePattern && + // Exclude current pattern. + currentPatternName !== pattern.name && + pattern.categories?.some( ( category ) => { + return categories.includes( category ); + } ) && + // Check if the pattern is not a synced pattern. + ( pattern.syncStatus === 'unsynced' || ! pattern.id ) + ); + } ) + .slice( 0, MAX_PATTERNS_TO_SHOW ); + }, [ categories, currentPatternName, patterns ] ); + + const currentShownPatterns = useAsyncList( + sameCategoryPatternsWithSingleWrapper + ); + + if ( sameCategoryPatternsWithSingleWrapper.length < 2 ) { + return null; + } + + const onClickPattern = ( pattern ) => { + const newBlocks = ( pattern.blocks ?? [] ).map( ( block ) => { + return cloneBlock( block ); + } ); + newBlocks[ 0 ].attributes.metadata = { + ...newBlocks[ 0 ].attributes.metadata, + categories, + }; + replaceBlocks( clientId, newBlocks ); + }; + + return ( + { + return ( + + onToggle( ! isOpen ) } + aria-expanded={ isOpen } + > + { __( 'Change design' ) } + + + ); + } } + renderContent={ () => ( + + + + ) } + /> + ); +} diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 77b10149aaf226..b3bf7e94accb08 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -16,7 +16,7 @@ import { isReusableBlock, isTemplatePart, } from '@wordpress/blocks'; -import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; +import { ToolbarGroup } from '@wordpress/components'; /** * Internal dependencies @@ -35,7 +35,7 @@ import { store as blockEditorStore } from '../../store'; import __unstableBlockNameContext from './block-name-context'; import NavigableToolbar from '../navigable-toolbar'; import { useHasBlockToolbar } from './use-has-block-toolbar'; -import Shuffle from './shuffle'; +import ChangeDesign from './change-design'; import { unlock } from '../../lock-unlock'; /** @@ -220,12 +220,7 @@ export function PrivateBlockToolbar( { isMultiToolbar && showGroupButtons && } { showShuffleButton && ( - - - + ) } { shouldShowVisualToolbar && showSlots && ( <> diff --git a/packages/block-editor/src/components/block-toolbar/shuffle.js b/packages/block-editor/src/components/block-toolbar/shuffle.js deleted file mode 100644 index 954c7ff22d68ce..00000000000000 --- a/packages/block-editor/src/components/block-toolbar/shuffle.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * WordPress dependencies - */ -import { shuffle } from '@wordpress/icons'; -import { ToolbarButton, ToolbarGroup } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useMemo } from '@wordpress/element'; -import { useSelect, useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as blockEditorStore } from '../../store'; - -const EMPTY_ARRAY = []; - -function Container( props ) { - return ( - - - - ); -} - -export default function Shuffle( { clientId, as = Container } ) { - const { categories, patterns, patternName } = useSelect( - ( select ) => { - const { - getBlockAttributes, - getBlockRootClientId, - __experimentalGetAllowedPatterns, - } = select( blockEditorStore ); - const attributes = getBlockAttributes( clientId ); - const _categories = attributes?.metadata?.categories || EMPTY_ARRAY; - const _patternName = attributes?.metadata?.patternName; - const rootBlock = getBlockRootClientId( clientId ); - - // Calling `__experimentalGetAllowedPatterns` is expensive. - // Checking if the block can be shuffled prevents unnecessary selector calls. - // See: https://github.com/WordPress/gutenberg/pull/64736. - const _patterns = - _categories.length > 0 - ? __experimentalGetAllowedPatterns( rootBlock ) - : EMPTY_ARRAY; - return { - categories: _categories, - patterns: _patterns, - patternName: _patternName, - }; - }, - [ clientId ] - ); - const { replaceBlocks } = useDispatch( blockEditorStore ); - const sameCategoryPatternsWithSingleWrapper = useMemo( () => { - if ( categories.length === 0 || ! patterns || patterns.length === 0 ) { - return EMPTY_ARRAY; - } - return patterns.filter( ( pattern ) => { - const isCorePattern = - pattern.source === 'core' || - ( pattern.source?.startsWith( 'pattern-directory' ) && - pattern.source !== 'pattern-directory/theme' ); - return ( - // Check if the pattern has only one top level block, - // otherwise we may shuffle to pattern that will not allow to continue shuffling. - pattern.blocks.length === 1 && - // We exclude the core patterns and pattern directory patterns that are not theme patterns. - ! isCorePattern && - pattern.categories?.some( ( category ) => { - return categories.includes( category ); - } ) && - // Check if the pattern is not a synced pattern. - ( pattern.syncStatus === 'unsynced' || ! pattern.id ) - ); - } ); - }, [ categories, patterns ] ); - - if ( sameCategoryPatternsWithSingleWrapper.length < 2 ) { - return null; - } - - function getNextPattern() { - const numberOfPatterns = sameCategoryPatternsWithSingleWrapper.length; - const patternIndex = sameCategoryPatternsWithSingleWrapper.findIndex( - ( { name } ) => name === patternName - ); - const nextPatternIndex = - patternIndex + 1 < numberOfPatterns ? patternIndex + 1 : 0; - return sameCategoryPatternsWithSingleWrapper[ nextPatternIndex ]; - } - - const ComponentToUse = as; - return ( - { - const nextPattern = getNextPattern(); - nextPattern.blocks[ 0 ].attributes = { - ...nextPattern.blocks[ 0 ].attributes, - metadata: { - ...nextPattern.blocks[ 0 ].attributes.metadata, - categories, - }, - }; - replaceBlocks( clientId, nextPattern.blocks ); - } } - /> - ); -} diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index ae03eeed1a817c..26bf71356925e9 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -285,3 +285,19 @@ } } } + +.block-editor-block-toolbar-change-design-content-wrapper { + padding: $grid-unit-15; + width: 320px; + .block-editor-block-patterns-list { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: $grid-unit-15; + .block-editor-block-patterns-list__list-item { + margin-bottom: 0; + } + .block-editor-inserter__media-list__list-item { + min-height: 100px; + } + } +}