Skip to content

Commit

Permalink
Template Parts: Add a replace flow to the inspector controls (#55128)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
scruffian authored Feb 22, 2024
1 parent 34c9431 commit 3bb8b94
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 11 deletions.
93 changes: 90 additions & 3 deletions packages/block-library/src/template-part/edit/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import {
BlockSettingsMenuControls,
useBlockProps,
Expand All @@ -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
Expand All @@ -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( {
Expand All @@ -43,7 +48,6 @@ function ReplaceButton( {
templatePartId
);
const blockPatterns = useAlternativeBlockPatterns( area, clientId );

const hasReplacements = !! templateParts.length || !! blockPatterns.length;
const canReplace =
isEntityAvailable &&
Expand All @@ -67,11 +71,29 @@ function ReplaceButton( {
);
}

function TemplatesList( { availableTemplates, onSelect } ) {
const shownTemplates = useAsyncList( availableTemplates );

if ( ! availableTemplates ) {
return null;
}

return (
<BlockPatternsList
label={ __( 'Templates' ) }
blockPatterns={ availableTemplates }
shownPatterns={ shownTemplates }
onClickPattern={ onSelect }
/>
);
}

export default function TemplatePartEdit( {
attributes,
setAttributes,
clientId,
} ) {
const { createSuccessNotice } = useDispatch( noticesStore );
const currentTheme = useSelect(
( select ) => select( coreStore ).getCurrentTheme()?.stylesheet,
[]
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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 (
<>
<RecursionProvider uniqueId={ templatePartId }>
Expand Down Expand Up @@ -207,6 +267,33 @@ export default function TemplatePartEdit( {
);
} }
</BlockSettingsMenuControls>

{ canReplace &&
( partsAsPatterns.length > 0 ||
blockPatterns.length > 0 ) && (
<InspectorControls>
<PanelBody title={ __( 'Replace' ) }>
<TemplatesList
availableTemplates={ partsAsPatterns }
onSelect={ ( pattern ) => {
onTemplatePartSelect(
pattern.templatePart
);
} }
/>
<TemplatesList
availableTemplates={ blockPatterns }
onSelect={ ( pattern, blocks ) => {
createFromBlocks(
blocks,
pattern.title
);
} }
/>
</PanelBody>
</InspectorControls>
) }

{ isEntityAvailable && (
<TemplatePartInnerBlocks
tagName={ TagName }
Expand Down
12 changes: 4 additions & 8 deletions packages/block-library/src/template-part/edit/selection-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useMemo, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { useDispatch } from '@wordpress/data';
import { parse } from '@wordpress/blocks';
import { useAsyncList } from '@wordpress/compose';
import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor';
import {
Expand All @@ -21,7 +20,7 @@ import {
useAlternativeTemplateParts,
useCreateTemplatePartFromBlocks,
} from './utils/hooks';
import { createTemplatePartId } from './utils/create-template-part-id';
import { mapTemplatePartToBlockPattern } from './utils/map-template-part-to-block-pattern';
import { searchPatterns } from '../../utils/search-patterns';

export default function TemplatePartSelectionModal( {
Expand All @@ -39,12 +38,9 @@ export default function TemplatePartSelectionModal( {
);
// We can map template parts to block patters to reuse the BlockPatternsList UI
const filteredTemplateParts = useMemo( () => {
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 ] );
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
};
}

1 comment on commit 3bb8b94

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 3bb8b94.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/8007467101
📝 Reported issues:

Please sign in to comment.