|
| 1 | +/** |
| 2 | + * WordPress dependencies |
| 3 | + */ |
| 4 | +import { |
| 5 | + ToolbarButton, |
| 6 | + ToolbarGroup, |
| 7 | + Dropdown, |
| 8 | + __experimentalDropdownContentWrapper as DropdownContentWrapper, |
| 9 | +} from '@wordpress/components'; |
| 10 | +import { __ } from '@wordpress/i18n'; |
| 11 | +import { cloneBlock } from '@wordpress/blocks'; |
| 12 | +import { useMemo } from '@wordpress/element'; |
| 13 | +import { useAsyncList } from '@wordpress/compose'; |
| 14 | +import { useSelect, useDispatch } from '@wordpress/data'; |
| 15 | + |
| 16 | +/** |
| 17 | + * Internal dependencies |
| 18 | + */ |
| 19 | +import { store as blockEditorStore } from '../../store'; |
| 20 | +import BlockPatternsList from '../block-patterns-list'; |
| 21 | + |
| 22 | +const EMPTY_ARRAY = []; |
| 23 | +const MAX_PATTERNS_TO_SHOW = 6; |
| 24 | +const POPOVER_PROPS = { |
| 25 | + placement: 'bottom-start', |
| 26 | +}; |
| 27 | + |
| 28 | +export default function ChangeDesign( { clientId } ) { |
| 29 | + const { categories, currentPatternName, patterns } = useSelect( |
| 30 | + ( select ) => { |
| 31 | + const { |
| 32 | + getBlockAttributes, |
| 33 | + getBlockRootClientId, |
| 34 | + __experimentalGetAllowedPatterns, |
| 35 | + } = select( blockEditorStore ); |
| 36 | + const attributes = getBlockAttributes( clientId ); |
| 37 | + const _categories = attributes?.metadata?.categories || EMPTY_ARRAY; |
| 38 | + const rootBlock = getBlockRootClientId( clientId ); |
| 39 | + |
| 40 | + // Calling `__experimentalGetAllowedPatterns` is expensive. |
| 41 | + // Checking if the block can be changed prevents unnecessary selector calls. |
| 42 | + // See: https://github.com/WordPress/gutenberg/pull/64736. |
| 43 | + const _patterns = |
| 44 | + _categories.length > 0 |
| 45 | + ? __experimentalGetAllowedPatterns( rootBlock ) |
| 46 | + : EMPTY_ARRAY; |
| 47 | + return { |
| 48 | + categories: _categories, |
| 49 | + currentPatternName: attributes?.metadata?.patternName, |
| 50 | + patterns: _patterns, |
| 51 | + }; |
| 52 | + }, |
| 53 | + [ clientId ] |
| 54 | + ); |
| 55 | + const { replaceBlocks } = useDispatch( blockEditorStore ); |
| 56 | + const sameCategoryPatternsWithSingleWrapper = useMemo( () => { |
| 57 | + if ( categories.length === 0 || ! patterns || patterns.length === 0 ) { |
| 58 | + return EMPTY_ARRAY; |
| 59 | + } |
| 60 | + return patterns |
| 61 | + .filter( ( pattern ) => { |
| 62 | + const isCorePattern = |
| 63 | + pattern.source === 'core' || |
| 64 | + ( pattern.source?.startsWith( 'pattern-directory' ) && |
| 65 | + pattern.source !== 'pattern-directory/theme' ); |
| 66 | + return ( |
| 67 | + // Check if the pattern has only one top level block, |
| 68 | + // otherwise we may switch to a pattern that doesn't have replacement suggestions. |
| 69 | + pattern.blocks.length === 1 && |
| 70 | + // We exclude the core patterns and pattern directory patterns that are not theme patterns. |
| 71 | + ! isCorePattern && |
| 72 | + // Exclude current pattern. |
| 73 | + currentPatternName !== pattern.name && |
| 74 | + pattern.categories?.some( ( category ) => { |
| 75 | + return categories.includes( category ); |
| 76 | + } ) && |
| 77 | + // Check if the pattern is not a synced pattern. |
| 78 | + ( pattern.syncStatus === 'unsynced' || ! pattern.id ) |
| 79 | + ); |
| 80 | + } ) |
| 81 | + .slice( 0, MAX_PATTERNS_TO_SHOW ); |
| 82 | + }, [ categories, currentPatternName, patterns ] ); |
| 83 | + |
| 84 | + const currentShownPatterns = useAsyncList( |
| 85 | + sameCategoryPatternsWithSingleWrapper |
| 86 | + ); |
| 87 | + |
| 88 | + if ( sameCategoryPatternsWithSingleWrapper.length < 2 ) { |
| 89 | + return null; |
| 90 | + } |
| 91 | + |
| 92 | + const onClickPattern = ( pattern ) => { |
| 93 | + const newBlocks = ( pattern.blocks ?? [] ).map( ( block ) => { |
| 94 | + return cloneBlock( block ); |
| 95 | + } ); |
| 96 | + newBlocks[ 0 ].attributes.metadata = { |
| 97 | + ...newBlocks[ 0 ].attributes.metadata, |
| 98 | + categories, |
| 99 | + }; |
| 100 | + replaceBlocks( clientId, newBlocks ); |
| 101 | + }; |
| 102 | + |
| 103 | + return ( |
| 104 | + <Dropdown |
| 105 | + popoverProps={ POPOVER_PROPS } |
| 106 | + renderToggle={ ( { onToggle, isOpen } ) => { |
| 107 | + return ( |
| 108 | + <ToolbarGroup> |
| 109 | + <ToolbarButton |
| 110 | + onClick={ () => onToggle( ! isOpen ) } |
| 111 | + aria-expanded={ isOpen } |
| 112 | + > |
| 113 | + { __( 'Change design' ) } |
| 114 | + </ToolbarButton> |
| 115 | + </ToolbarGroup> |
| 116 | + ); |
| 117 | + } } |
| 118 | + renderContent={ () => ( |
| 119 | + <DropdownContentWrapper |
| 120 | + className="block-editor-block-toolbar-change-design-content-wrapper" |
| 121 | + paddingSize="none" |
| 122 | + > |
| 123 | + <BlockPatternsList |
| 124 | + shownPatterns={ currentShownPatterns } |
| 125 | + blockPatterns={ sameCategoryPatternsWithSingleWrapper } |
| 126 | + onClickPattern={ onClickPattern } |
| 127 | + showTitle={ false } |
| 128 | + /> |
| 129 | + </DropdownContentWrapper> |
| 130 | + ) } |
| 131 | + /> |
| 132 | + ); |
| 133 | +} |
0 commit comments