From 3bb8b94f8962fd2dc8057f5ef240457360715e66 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 22 Feb 2024 17:07:00 +0100 Subject: [PATCH] Template Parts: Add a replace flow to the inspector controls (#55128) * Template Parts: Add a replace flow to the inspector controls * create a function to share the code that creates block patterns from template parts' * fix the template part selection * show patterns either way --- .../src/template-part/edit/index.js | 93 ++++++++++++++++++- .../src/template-part/edit/selection-modal.js | 12 +-- .../map-template-part-to-block-pattern.js | 23 +++++ 3 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 packages/block-library/src/template-part/edit/utils/map-template-part-to-block-pattern.js diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js index 70078e5935836..4523c58cfd130 100644 --- a/packages/block-library/src/template-part/edit/index.js +++ b/packages/block-library/src/template-part/edit/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { BlockSettingsMenuControls, useBlockProps, @@ -10,11 +10,14 @@ import { RecursionProvider, useHasRecursion, InspectorControls, + __experimentalBlockPatternsList as BlockPatternsList, } from '@wordpress/block-editor'; -import { Spinner, Modal, MenuItem } from '@wordpress/components'; +import { PanelBody, Spinner, Modal, MenuItem } from '@wordpress/components'; +import { useAsyncList } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; import { useState } from '@wordpress/element'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -24,10 +27,12 @@ import TemplatePartSelectionModal from './selection-modal'; import { TemplatePartAdvancedControls } from './advanced-controls'; import TemplatePartInnerBlocks from './inner-blocks'; import { createTemplatePartId } from './utils/create-template-part-id'; +import { mapTemplatePartToBlockPattern } from './utils/map-template-part-to-block-pattern'; import { useAlternativeBlockPatterns, useAlternativeTemplateParts, useTemplatePartArea, + useCreateTemplatePartFromBlocks, } from './utils/hooks'; function ReplaceButton( { @@ -43,7 +48,6 @@ function ReplaceButton( { templatePartId ); const blockPatterns = useAlternativeBlockPatterns( area, clientId ); - const hasReplacements = !! templateParts.length || !! blockPatterns.length; const canReplace = isEntityAvailable && @@ -67,11 +71,29 @@ function ReplaceButton( { ); } +function TemplatesList( { availableTemplates, onSelect } ) { + const shownTemplates = useAsyncList( availableTemplates ); + + if ( ! availableTemplates ) { + return null; + } + + return ( + + ); +} + export default function TemplatePartEdit( { attributes, setAttributes, clientId, } ) { + const { createSuccessNotice } = useDispatch( noticesStore ); const currentTheme = useSelect( ( select ) => select( coreStore ).getCurrentTheme()?.stylesheet, [] @@ -117,12 +139,28 @@ export default function TemplatePartEdit( { [ templatePartId, attributes.area, clientId ] ); + const { templateParts } = useAlternativeTemplateParts( + area, + templatePartId + ); + const blockPatterns = useAlternativeBlockPatterns( area, clientId ); + const hasReplacements = !! templateParts.length || !! blockPatterns.length; const areaObject = useTemplatePartArea( area ); const blockProps = useBlockProps(); const isPlaceholder = ! slug; const isEntityAvailable = ! isPlaceholder && ! isMissing && isResolved; const TagName = tagName || areaObject.tagName; + const canReplace = + isEntityAvailable && + hasReplacements && + ( area === 'header' || area === 'footer' ); + + const createFromBlocks = useCreateTemplatePartFromBlocks( + area, + setAttributes + ); + // We don't want to render a missing state if we have any inner blocks. // A new template part is automatically created if we have any inner blocks but no entity. if ( @@ -154,6 +192,28 @@ export default function TemplatePartEdit( { ); } + const partsAsPatterns = templateParts.map( ( templatePart ) => + mapTemplatePartToBlockPattern( templatePart ) + ); + + const onTemplatePartSelect = ( templatePart ) => { + setAttributes( { + slug: templatePart.slug, + theme: templatePart.theme, + area: undefined, + } ); + createSuccessNotice( + sprintf( + /* translators: %s: template part title. */ + __( 'Template Part "%s" replaceed.' ), + templatePart.title?.rendered || templatePart.slug + ), + { + type: 'snackbar', + } + ); + }; + return ( <> @@ -207,6 +267,33 @@ export default function TemplatePartEdit( { ); } } + + { canReplace && + ( partsAsPatterns.length > 0 || + blockPatterns.length > 0 ) && ( + + + { + onTemplatePartSelect( + pattern.templatePart + ); + } } + /> + { + createFromBlocks( + blocks, + pattern.title + ); + } } + /> + + + ) } + { isEntityAvailable && ( { - const partsAsPatterns = templateParts.map( ( templatePart ) => ( { - name: createTemplatePartId( templatePart.theme, templatePart.slug ), - title: templatePart.title.rendered, - blocks: parse( templatePart.content.raw ), - templatePart, - } ) ); + const partsAsPatterns = templateParts.map( ( templatePart ) => + mapTemplatePartToBlockPattern( templatePart ) + ); return searchPatterns( partsAsPatterns, searchValue ); }, [ templateParts, searchValue ] ); diff --git a/packages/block-library/src/template-part/edit/utils/map-template-part-to-block-pattern.js b/packages/block-library/src/template-part/edit/utils/map-template-part-to-block-pattern.js new file mode 100644 index 0000000000000..5a053350d750f --- /dev/null +++ b/packages/block-library/src/template-part/edit/utils/map-template-part-to-block-pattern.js @@ -0,0 +1,23 @@ +/** + * WordPress dependencies + */ +import { parse } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { createTemplatePartId } from './create-template-part-id'; + +/** + * This maps the properties of a template part to those of a block pattern. + * @param {Object} templatePart + * @return {Object} The template part in the shape of block pattern. + */ +export function mapTemplatePartToBlockPattern( templatePart ) { + return { + name: createTemplatePartId( templatePart.theme, templatePart.slug ), + title: templatePart.title.rendered, + blocks: parse( templatePart.content.raw ), + templatePart, + }; +}