diff --git a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/archive-product.ts b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/archive-product.ts index e9a6b4bb30f..f38439ae12a 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/archive-product.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/archive-product.ts @@ -19,7 +19,7 @@ import { } from '../product-query/constants'; import { VARIATION_NAME as productsVariationName } from '../product-query/variations/product-query'; import { createArchiveTitleBlock, createRowBlock } from './utils'; -import { type InheritedAttributes } from './types'; +import { OnClickCallbackParameter, type InheritedAttributes } from './types'; const createProductsBlock = ( inheritedAttributes: InheritedAttributes ) => createBlock( @@ -71,7 +71,7 @@ const getDescriptionAllowingConversion = ( templateTitle: string ) => sprintf( /* translators: %s is the template title */ __( - "This block serves as a placeholder for your %s. We recommend upgrading to the Products block for more features to edit your products visually. Don't worry, you can always revert back.", + 'Transform this template into multiple blocks so you can add, remove, reorder, and customize your %s template.', 'woo-gutenberg-products-block' ), templateTitle @@ -96,17 +96,69 @@ const getDescription = ( templateTitle: string, canConvert: boolean ) => { }; const getButtonLabel = () => - __( 'Upgrade to Products block', 'woo-gutenberg-products-block' ); + __( 'Transform into blocks', 'woo-gutenberg-products-block' ); + +const onClickCallback = ( { + clientId, + attributes, + getBlocks, + replaceBlock, + selectBlock, +}: OnClickCallbackParameter ) => { + replaceBlock( clientId, getBlockifiedTemplate( attributes ) ); + + const blocks = getBlocks(); + + const groupBlock = blocks.find( + ( block ) => + block.name === 'core/group' && + block.innerBlocks.some( + ( innerBlock ) => + innerBlock.name === 'woocommerce/store-notices' + ) + ); + + if ( groupBlock ) { + selectBlock( groupBlock.clientId ); + } +}; + +const onClickCallbackWithTermDescription = ( { + clientId, + attributes, + getBlocks, + replaceBlock, + selectBlock, +}: OnClickCallbackParameter ) => { + replaceBlock( clientId, getBlockifiedTemplate( attributes, true ) ); + + const blocks = getBlocks(); + + const groupBlock = blocks.find( + ( block ) => + block.name === 'core/group' && + block.innerBlocks.some( + ( innerBlock ) => + innerBlock.name === 'woocommerce/store-notices' + ) + ); + + if ( groupBlock ) { + selectBlock( groupBlock.clientId ); + } +}; export const blockifiedProductCatalogConfig = { getBlockifiedTemplate, isConversionPossible, getDescription, getButtonLabel, + onClickCallback, }; export const blockifiedProductTaxonomyConfig = { getBlockifiedTemplate: getBlockifiedTemplateWithTermDescription, + onClickCallback: onClickCallbackWithTermDescription, isConversionPossible, getDescription, getButtonLabel, diff --git a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/editor.scss b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/editor.scss index 9ff1b017c70..c2997f22d81 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/editor.scss +++ b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/editor.scss @@ -1,12 +1,14 @@ :where(.wp-block-woocommerce-legacy-template) { margin-left: auto; margin-right: auto; - max-width: 1000px; } .wp-block-woocommerce-classic-template__placeholder-copy { + display: flex; + flex-direction: column; max-width: 900px; - margin-bottom: 30px; + width: 400px; + margin: auto; } .wp-block-woocommerce-classic-template__placeholder-warning { @@ -15,9 +17,12 @@ } .wp-block-woocommerce-classic-template__placeholder-wireframe { - width: 100%; height: 250px; background: #e5e5e5; + display: flex; + flex-wrap: wrap; + gap: $gap-large; + margin: auto; @media only screen and (min-width: 768px) { height: auto; @@ -27,7 +32,7 @@ .wp-block-woocommerce-classic-template__placeholder .wp-block-woocommerce-classic-template__placeholder-image { display: none; - width: 100%; + width: 400px; height: auto; @media only screen and (min-width: 768px) { @@ -35,14 +40,18 @@ } } - -.wp-block-woocommerce-classic-template__placeholder-wireframe { - display: flex; - flex-direction: column; -} - .wp-block-woocommerce-classic-template__placeholder-migration-button-container { justify-content: center; align-items: center; margin: 0 auto; } + +.wp-block-woocommerce-classic-template__placeholder-copy__icon-container { + display: flex; + align-items: center; + gap: $gap-small; + + span { + @include font-size(larger); + } +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/index.tsx index 2df71d93f5e..9271db20e14 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/index.tsx @@ -2,6 +2,8 @@ * External dependencies */ import { + BlockInstance, + createBlock, getBlockType, registerBlockType, unregisterBlockType, @@ -11,12 +13,18 @@ import { isExperimentalBuild, WC_BLOCKS_IMAGE_URL, } from '@woocommerce/block-settings'; -import { useBlockProps } from '@wordpress/block-editor'; -import { Button, Placeholder } from '@wordpress/components'; +import { + useBlockProps, + BlockPreview, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { Button, Placeholder, Popover } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { box, Icon } from '@wordpress/icons'; -import { select, useDispatch, subscribe } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useDispatch, subscribe, useSelect, select } from '@wordpress/data'; +import { useEffect, useMemo, useState } from '@wordpress/element'; +import { store as noticesStore } from '@wordpress/notices'; +import { useEntityRecord } from '@wordpress/core-data'; /** * Internal dependencies @@ -47,6 +55,7 @@ const blockifiedFallbackConfig = { getBlockifiedTemplate: () => [], getDescription: () => '', getButtonLabel: () => '', + onClickCallback: () => void 0, }; const conversionConfig: { [ key: string ]: BlockifiedTemplateConfig } = { @@ -57,19 +66,53 @@ const conversionConfig: { [ key: string ]: BlockifiedTemplateConfig } = { fallback: blockifiedFallbackConfig, }; +const pickBlockClientIds = ( blocks: Array< BlockInstance > ) => + blocks.reduce< Array< string > >( ( acc, block ) => { + if ( block.name === 'core/template-part' ) { + return acc; + } + + return [ ...acc, block.clientId ]; + }, [] ); + const Edit = ( { clientId, attributes, setAttributes, }: BlockEditProps< Attributes > ) => { - const { replaceBlock } = useDispatch( 'core/block-editor' ); + const { replaceBlock, selectBlock, replaceBlocks } = + useDispatch( blockEditorStore ); + + const { getBlocks, editedPostId } = useSelect( ( sel ) => { + return { + getBlocks: sel( blockEditorStore ).getBlocks, + editedPostId: sel( 'core/edit-site' ).getEditedPostId(), + }; + }, [] ); + + const template = useEntityRecord< { + slug: string; + title: { + rendered?: string; + row: string; + }; + } >( 'postType', 'wp_template', editedPostId ); + + const { createInfoNotice } = useDispatch( noticesStore ); + + const blocks = getBlocks(); + + const clientIds = useMemo( () => { + pickBlockClientIds( blocks ); + }, [ blocks ] ); const blockProps = useBlockProps(); const templateDetails = getTemplateDetailsBySlug( attributes.template, TEMPLATES ); - const templateTitle = templateDetails?.title ?? attributes.template; + const templateTitle = + template.record?.title.rendered?.toLowerCase() ?? attributes.template; const templatePlaceholder = templateDetails?.placeholder ?? 'fallback'; const templateType = templateDetails?.type ?? 'fallback'; @@ -83,40 +126,128 @@ const Edit = ( { ); const { - getBlockifiedTemplate, isConversionPossible, getDescription, getButtonLabel, + onClickCallback, + getBlockifiedTemplate, } = conversionConfig[ templateType ]; const canConvert = isConversionPossible(); const placeholderDescription = getDescription( templateTitle, canConvert ); + const [ isPopoverOpen, setIsPopoverOpen ] = useState( false ); return (
- -
-

{ placeholderDescription }

-
+
- { canConvert && ( -
- +
+ ) } +
createBlock( 'core/paragraph', { @@ -118,7 +118,7 @@ const getDescriptionAllowingConversion = ( templateTitle: string ) => sprintf( /* translators: %s is the template title */ __( - "This block serves as a placeholder for your %s. We recommend upgrading to the Products block for more features to edit your products visually. Don't worry, you can always revert back.", + 'Transform this template into multiple blocks so you can add, remove, reorder, and customize your %s template.', 'woo-gutenberg-products-block' ), templateTitle @@ -142,12 +142,38 @@ const getDescription = ( templateTitle: string, canConvert: boolean ) => { return getDescriptionDisallowingConversion( templateTitle ); }; +const onClickCallback = ( { + clientId, + attributes, + getBlocks, + replaceBlock, + selectBlock, +}: OnClickCallbackParameter ) => { + replaceBlock( clientId, getBlockifiedTemplate( attributes ) ); + + const blocks = getBlocks(); + + const groupBlock = blocks.find( + ( block ) => + block.name === 'core/group' && + block.innerBlocks.some( + ( innerBlock ) => + innerBlock.name === 'woocommerce/store-notices' + ) + ); + + if ( groupBlock ) { + selectBlock( groupBlock.clientId ); + } +}; + const getButtonLabel = () => - __( 'Upgrade to Products block', 'woo-gutenberg-products-block' ); + __( 'Transform into blocks', 'woo-gutenberg-products-block' ); export { getBlockifiedTemplate, isConversionPossible, getDescription, getButtonLabel, + onClickCallback, }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/single-product.ts b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/single-product.ts index 0c8f434d35e..1e907e7e3b8 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/single-product.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/single-product.ts @@ -7,6 +7,11 @@ import { BlockInstance, createBlock } from '@wordpress/blocks'; import { VARIATION_NAME as PRODUCT_TITLE_VARIATION_NAME } from '@woocommerce/blocks/product-query/variations/elements/product-title'; import { VARIATION_NAME as PRODUCT_SUMMARY_VARIATION_NAME } from '@woocommerce/blocks/product-query/variations/elements/product-summary'; +/** + * Internal dependencies + */ +import { OnClickCallbackParameter } from './types'; + const getBlockifiedTemplate = () => [ createBlock( 'woocommerce/breadcrumbs' ), @@ -58,7 +63,7 @@ const getDescriptionAllowingConversion = ( templateTitle: string ) => sprintf( /* translators: %s is the template title */ __( - "This block serves as a placeholder for your %s. We recommend upgrading to the Single Products block for more features to edit your products visually. Don't worry, you can always revert back.", + 'Transform this template into multiple blocks so you can add, remove, reorder, and customize your %s.', 'woo-gutenberg-products-block' ), templateTitle @@ -83,14 +88,34 @@ const getDescription = ( templateTitle: string, canConvert: boolean ) => { }; const getButtonLabel = () => - __( - 'Upgrade to Blockified Single Product template', - 'woo-gutenberg-products-block' + __( 'Transform into blocks', 'woo-gutenberg-products-block' ); + +const onClickCallback = ( { + clientId, + getBlocks, + replaceBlock, + selectBlock, +}: OnClickCallbackParameter ) => { + replaceBlock( clientId, getBlockifiedTemplate() ); + + const blocks = getBlocks(); + const groupBlock = blocks.find( + ( block ) => + block.name === 'core/group' && + block.innerBlocks.some( + ( innerBlock ) => innerBlock.name === 'woocommerce/breadcrumbs' + ) ); + if ( groupBlock ) { + selectBlock( groupBlock.clientId ); + } +}; + export { getBlockifiedTemplate, isConversionPossible, getDescription, getButtonLabel, + onClickCallback, }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/types.ts index 41cb7d61006..f9d2fd05e54 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/classic-template/types.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/classic-template/types.ts @@ -9,6 +9,14 @@ export type InheritedAttributes = { align?: string; }; +export type OnClickCallbackParameter = { + clientId: string; + attributes: Record< string, unknown >; + getBlocks: () => BlockInstance[]; + replaceBlock: ( clientId: string, blocks: BlockInstance[] ) => void; + selectBlock: ( clientId: string ) => void; +}; + export type BlockifiedTemplateConfig = { getBlockifiedTemplate: ( inheritedAttributes: InheritedAttributes @@ -16,4 +24,5 @@ export type BlockifiedTemplateConfig = { isConversionPossible: () => boolean; getDescription: ( templateTitle: string, canConvert: boolean ) => string; getButtonLabel: () => string; + onClickCallback: ( params: OnClickCallbackParameter ) => void; }; diff --git a/plugins/woocommerce-blocks/assets/js/index.js b/plugins/woocommerce-blocks/assets/js/index.js index fba44fb3fd6..5fdfbdcc91c 100644 --- a/plugins/woocommerce-blocks/assets/js/index.js +++ b/plugins/woocommerce-blocks/assets/js/index.js @@ -5,6 +5,7 @@ import { getCategories, setCategories } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; import { woo } from '@woocommerce/icons'; import { Icon } from '@wordpress/icons'; +import '@woocommerce/templates/revert-button'; /** * Internal dependencies diff --git a/plugins/woocommerce-blocks/assets/js/templates/revert-button/index.tsx b/plugins/woocommerce-blocks/assets/js/templates/revert-button/index.tsx new file mode 100644 index 00000000000..505c613ba30 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/templates/revert-button/index.tsx @@ -0,0 +1,171 @@ +/** + * External dependencies + */ +import { PluginTemplateSettingPanel } from '@wordpress/edit-site'; +import { subscribe, select, useSelect, useDispatch } from '@wordpress/data'; +import { BlockInstance, createBlock } from '@wordpress/blocks'; +import { Button, PanelBody } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { createInterpolateElement, useMemo } from '@wordpress/element'; +import { useEntityRecord } from '@wordpress/core-data'; +import { store as blockEditorStore } from '@wordpress/block-editor'; + +// @ts-expect-error: @wordpress/plugin is typed in the newer versions +// eslint-disable-next-line @woocommerce/dependency-group +import { + registerPlugin, + unregisterPlugin, + getPlugin, +} from '@wordpress/plugins'; + +/** + * Internal dependencies + */ +import './style.scss'; + +const hasLegacyTemplateBlock = ( blocks: Array< BlockInstance > ): boolean => { + return blocks.some( ( block ) => { + return ( + block.name === 'woocommerce/legacy-template' || + hasLegacyTemplateBlock( block.innerBlocks ) + ); + } ); +}; + +const pickBlockClientIds = ( blocks: Array< BlockInstance > ) => + blocks.reduce< Array< string > >( ( acc, block ) => { + if ( block.name === 'core/template-part' ) { + return acc; + } + + return [ ...acc, block.clientId ]; + }, [] ); + +const RevertClassicTemplateButton = () => { + const { blocks, editedPostId } = useSelect( ( sel ) => { + return { + blocks: sel( blockEditorStore ).getBlocks(), + editedPostId: sel( 'core/edit-site' ).getEditedPostId(), + }; + }, [] ); + + const { replaceBlocks } = useDispatch( blockEditorStore ); + + const template = useEntityRecord< { + slug: string; + title: { + rendered?: string; + row: string; + }; + } >( 'postType', 'wp_template', editedPostId ); + + const isLegacyTemplateBlockAdded = useMemo( + () => hasLegacyTemplateBlock( blocks ), + [ blocks ] + ); + + const clientIds = useMemo( () => pickBlockClientIds( blocks ), [ blocks ] ); + + return ( + <> + { ! isLegacyTemplateBlockAdded && ( + + + + + { createInterpolateElement( + __( + `The template doesn’t allow for reordering or customizing blocks, but might work better with your extensions`, + 'woo-gutenberg-products-block' + ), + { + strongText: ( + + { template?.record?.title + ?.rendered ?? '' } + + ), + } + ) } + + + + ) } + + ); +}; + +const templateSlugs = [ + 'single-product', + 'archive-product', + 'product-search-results', + 'taxonomy-product_cat', + 'taxonomy-product_tag', + 'taxonomy-product_attribute', +]; + +const REVERT_BUTTON_PLUGIN_NAME = 'woocommerce-blocks-revert-button-templates'; + +let currentTemplateId: string | undefined; +subscribe( () => { + const previousTemplateId = currentTemplateId; + const store = select( 'core/edit-site' ); + currentTemplateId = store.getEditedPostId(); + + if ( previousTemplateId === currentTemplateId ) { + return; + } + + const isWooTemplate = templateSlugs.some( ( slug ) => + currentTemplateId?.includes( slug ) + ); + + const hasSupportForPluginTemplateSettingPanel = + PluginTemplateSettingPanel !== undefined; + + if ( isWooTemplate && hasSupportForPluginTemplateSettingPanel ) { + if ( getPlugin( REVERT_BUTTON_PLUGIN_NAME ) ) { + return; + } + + return registerPlugin( REVERT_BUTTON_PLUGIN_NAME, { + render: RevertClassicTemplateButton, + } ); + } + + if ( getPlugin( REVERT_BUTTON_PLUGIN_NAME ) === undefined ) { + return; + } + + unregisterPlugin( REVERT_BUTTON_PLUGIN_NAME ); +}, 'core/edit-site' ); diff --git a/plugins/woocommerce-blocks/assets/js/templates/revert-button/style.scss b/plugins/woocommerce-blocks/assets/js/templates/revert-button/style.scss new file mode 100644 index 00000000000..f35c0366af4 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/templates/revert-button/style.scss @@ -0,0 +1,8 @@ +.wc-block-editor-revert-button-container { + display: flex; + flex-direction: column; + gap: $gap; + span { + color: $gray-700; + } +} diff --git a/plugins/woocommerce-blocks/bin/webpack-helpers.js b/plugins/woocommerce-blocks/bin/webpack-helpers.js index 5dad251446a..0eaa4926eb0 100644 --- a/plugins/woocommerce-blocks/bin/webpack-helpers.js +++ b/plugins/woocommerce-blocks/bin/webpack-helpers.js @@ -89,6 +89,10 @@ const getAlias = ( options = {} ) => { ), '@woocommerce/types': path.resolve( __dirname, `../assets/js/types/` ), '@woocommerce/utils': path.resolve( __dirname, `../assets/js/utils/` ), + '@woocommerce/templates': path.resolve( + __dirname, + `../assets/js/templates/` + ), }; }; diff --git a/plugins/woocommerce-blocks/tsconfig.base.json b/plugins/woocommerce-blocks/tsconfig.base.json index d9bd8e1d42f..849f04663a0 100644 --- a/plugins/woocommerce-blocks/tsconfig.base.json +++ b/plugins/woocommerce-blocks/tsconfig.base.json @@ -65,7 +65,7 @@ "@woocommerce/e2e-utils": [ "tests/e2e-pw/utils" ], "@woocommerce/e2e-types": [ "tests/e2e-pw/types" ], "@woocommerce/e2e-playwright-utils": [ "tests/e2e-pw/playwright-utils" ], - + "@woocommerce/templates/*": [ "assets/js/templates/*" ], } } }