From 283f4e7bb063656ef80425bd2208d8341dea64ec Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 3 Oct 2023 21:13:48 +0100 Subject: [PATCH] Add template replace flow to template inspector (#54609) * Add a modal to allow template switching * fetch template info * Allow switching to different patterns * Allow switching to different patterns * Add columns * move availble templates to the actions * filter for the correct templates * create the right data structure in the use select * move to a hook * inject theme attribute into pattern again * put the overlay over the top of the dropdown * fix the pattern to templates hook * set the template on click * Also set the blocks * remove calls to set template with the current template, since setting blocks correctly updates the content in the editor * serialize blocks so that we have correctly processed template parts * remove duplicated code * Remove unnecessary mapping * refactor * memoize the patterns * combine the useSelect * Update packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js Co-authored-by: Andrei Draganescu * Fix ESLint error * Only show the button is there is more than 1 pattern * Copy update * Move the hook to a subdir * check that there are patterns * move the check * remove useCallback * change condition to show the button * change condition * move to use editEntityRecord * combine filters * add comments * Update packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js Co-authored-by: Andrei Draganescu --------- Co-authored-by: Andrei Draganescu Co-authored-by: Andrei Draganescu Co-authored-by: George Mamadashvili --- packages/base-styles/_z-index.scss | 1 + .../sidebar-edit-mode/template-panel/hooks.js | 97 +++++++++++++++++++ .../template-panel/replace-template-button.js | 89 +++++++++++++++++ .../template-panel/style.scss | 18 ++++ .../template-panel/template-actions.js | 39 +++++--- 5 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/template-panel/hooks.js create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 12443a30a9665..cbe495d3787cd 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -128,6 +128,7 @@ $z-layers: ( ".block-editor-block-rename-modal": 1000001, ".edit-site-list__rename-modal": 1000001, ".edit-site-swap-template-modal": 1000001, + ".edit-site-template-panel__replace-template-modal": 1000001, // Note: The ConfirmDialog component's z-index is being set to 1000001 in packages/components/src/confirm-dialog/styles.ts // because it uses emotion and not sass. We need it to render on top its parent popover. diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/hooks.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/hooks.js new file mode 100644 index 0000000000000..b5e5988491396 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/hooks.js @@ -0,0 +1,97 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; +import { parse } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../../store'; +import { PATTERN_CORE_SOURCES, PATTERN_TYPES } from '../../../utils/constants'; +import { unlock } from '../../../lock-unlock'; + +function injectThemeAttributeInBlockTemplateContent( + block, + currentThemeStylesheet +) { + block.innerBlocks = block.innerBlocks.map( ( innerBlock ) => { + return injectThemeAttributeInBlockTemplateContent( + innerBlock, + currentThemeStylesheet + ); + } ); + + if ( + block.name === 'core/template-part' && + block.attributes.theme === undefined + ) { + block.attributes.theme = currentThemeStylesheet; + } + return block; +} + +function preparePatterns( patterns, template, currentThemeStylesheet ) { + // Filter out duplicates. + const filterOutDuplicatesByName = ( currentItem, index, items ) => + index === items.findIndex( ( item ) => currentItem.name === item.name ); + + // Filter out core patterns. + const filterOutCorePatterns = ( pattern ) => + ! PATTERN_CORE_SOURCES.includes( pattern.source ); + + // Filter only the patterns that are compatible with the current template. + const filterCompatiblePatterns = ( pattern ) => + pattern.templateTypes?.includes( template.slug ); + + return patterns + .filter( + filterOutCorePatterns && + filterOutDuplicatesByName && + filterCompatiblePatterns + ) + .map( ( pattern ) => ( { + ...pattern, + keywords: pattern.keywords || [], + type: PATTERN_TYPES.theme, + blocks: parse( pattern.content, { + __unstableSkipMigrationLogs: true, + } ).map( ( block ) => + injectThemeAttributeInBlockTemplateContent( + block, + currentThemeStylesheet + ) + ), + } ) ); +} + +export function useAvailablePatterns( template ) { + const { blockPatterns, restBlockPatterns, currentThemeStylesheet } = + useSelect( ( select ) => { + const { getSettings } = unlock( select( editSiteStore ) ); + const settings = getSettings(); + + return { + blockPatterns: + settings.__experimentalAdditionalBlockPatterns ?? + settings.__experimentalBlockPatterns, + restBlockPatterns: select( coreStore ).getBlockPatterns(), + currentThemeStylesheet: + select( coreStore ).getCurrentTheme().stylesheet, + }; + }, [] ); + + return useMemo( () => { + const mergedPatterns = [ + ...( blockPatterns || [] ), + ...( restBlockPatterns || [] ), + ]; + return preparePatterns( + mergedPatterns, + template, + currentThemeStylesheet + ); + }, [ blockPatterns, restBlockPatterns, template, currentThemeStylesheet ] ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js new file mode 100644 index 0000000000000..658aacd331deb --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js @@ -0,0 +1,89 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; +import { MenuItem, Modal } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { store as coreStore } from '@wordpress/core-data'; +import { useAsyncList } from '@wordpress/compose'; +import { serialize } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../../store'; + +export default function ReplaceTemplateButton( { + onClick, + availableTemplates, +} ) { + const { editEntityRecord } = useDispatch( coreStore ); + const [ showModal, setShowModal ] = useState( false ); + const onClose = () => { + setShowModal( false ); + }; + + const { postId, postType } = useSelect( ( select ) => { + return { + postId: select( editSiteStore ).getEditedPostId(), + postType: select( editSiteStore ).getEditedPostType(), + }; + }, [] ); + + const onTemplateSelect = async ( selectedTemplate ) => { + onClose(); // Close the template suggestions modal first. + onClick(); + await editEntityRecord( 'postType', postType, postId, { + blocks: selectedTemplate.blocks, + content: serialize( selectedTemplate.blocks ), + } ); + }; + + if ( ! availableTemplates.length || availableTemplates.length < 1 ) { + return null; + } + + return ( + <> + setShowModal( true ) } + > + { __( 'Replace template' ) } + + + { showModal && ( + +
+ +
+
+ ) } + + ); +} + +function TemplatesList( { availableTemplates, onSelect } ) { + const shownTemplates = useAsyncList( availableTemplates ); + + return ( + + ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/style.scss b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/style.scss index 4c8ef94855dcb..6eab753e8ad28 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/style.scss +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/style.scss @@ -37,3 +37,21 @@ h3.edit-site-template-card__template-areas-title { font-weight: 500; margin: 0 0 $grid-unit-10; } + + +.edit-site-template-panel__replace-template-modal { + z-index: z-index(".edit-site-template-panel__replace-template-modal"); +} + +.edit-site-template-panel__replace-template-modal__content { + column-count: 2; + column-gap: $grid-unit-30; + + @include break-medium() { + column-count: 3; + } + + @include break-wide() { + column-count: 4; + } +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/template-actions.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/template-actions.js index b68cf1ff61757..81acb244a1186 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/template-actions.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/template-actions.js @@ -11,13 +11,21 @@ import { moreVertical } from '@wordpress/icons'; */ import { store as editSiteStore } from '../../../store'; import isTemplateRevertable from '../../../utils/is-template-revertable'; +import ReplaceTemplateButton from './replace-template-button'; +import { useAvailablePatterns } from './hooks'; export default function Actions( { template } ) { + const availablePatterns = useAvailablePatterns( template ); const { revertTemplate } = useDispatch( editSiteStore ); const isRevertable = isTemplateRevertable( template ); - if ( ! isRevertable ) { + + if ( + ! isRevertable && + ( ! availablePatterns.length || availablePatterns.length < 1 ) + ) { return null; } + return ( { ( { onClose } ) => ( - { - revertTemplate( template ); - onClose(); - } } - > - { __( 'Clear customizations' ) } - + { isRevertable && ( + { + revertTemplate( template ); + onClose(); + } } + > + { __( 'Clear customizations' ) } + + ) } + ) }