2021-10-29 07:31:50 +00:00
/ * *
* External dependencies
* /
2022-06-29 07:42:02 +00:00
import {
2023-05-10 11:02:33 +00:00
BlockInstance ,
createBlock ,
2022-06-29 07:42:02 +00:00
getBlockType ,
registerBlockType ,
unregisterBlockType ,
2023-06-12 16:55:20 +00:00
parse ,
2022-06-29 07:42:02 +00:00
} from '@wordpress/blocks' ;
2022-11-02 16:46:14 +00:00
import type { BlockEditProps } from '@wordpress/blocks' ;
2023-05-25 07:42:29 +00:00
import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings' ;
2023-05-10 11:02:33 +00:00
import {
useBlockProps ,
BlockPreview ,
store as blockEditorStore ,
} from '@wordpress/block-editor' ;
import { Button , Placeholder , Popover } from '@wordpress/components' ;
2023-02-27 14:34:18 +00:00
import { __ } from '@wordpress/i18n' ;
2021-12-10 10:07:10 +00:00
import { box , Icon } from '@wordpress/icons' ;
2023-06-12 16:55:20 +00:00
import {
useDispatch ,
subscribe ,
useSelect ,
select ,
dispatch ,
} from '@wordpress/data' ;
2023-05-17 13:25:02 +00:00
import { useEffect , useState } from '@wordpress/element' ;
2023-05-10 11:02:33 +00:00
import { store as noticesStore } from '@wordpress/notices' ;
import { useEntityRecord } from '@wordpress/core-data' ;
2023-06-12 16:55:20 +00:00
import { debounce } from '@woocommerce/base-utils' ;
2023-06-29 13:41:22 +00:00
import { woo } from '@woocommerce/icons' ;
2023-08-04 14:16:19 +00:00
import { isNumber } from '@woocommerce/types' ;
2021-11-02 14:42:07 +00:00
/ * *
* Internal dependencies
* /
import './editor.scss' ;
2022-05-04 11:02:42 +00:00
import './style.scss' ;
2023-02-27 14:34:18 +00:00
import { BLOCK_SLUG , TEMPLATES , TYPES } from './constants' ;
2022-09-06 09:52:33 +00:00
import {
isClassicTemplateBlockRegisteredWithAnotherTitle ,
hasTemplateSupportForClassicTemplateBlock ,
getTemplateDetailsBySlug ,
} from './utils' ;
2023-02-27 14:34:18 +00:00
import {
blockifiedProductCatalogConfig ,
blockifiedProductTaxonomyConfig ,
} from './archive-product' ;
import * as blockifiedSingleProduct from './single-product' ;
import * as blockifiedProductSearchResults from './product-search-results' ;
2023-06-29 13:41:22 +00:00
import * as blockifiedOrderConfirmation from './order-confirmation' ;
2023-02-27 14:34:18 +00:00
import type { BlockifiedTemplateConfig } from './types' ;
2022-06-29 07:42:02 +00:00
type Attributes = {
template : string ;
align : string ;
} ;
2021-10-29 07:31:50 +00:00
2023-02-27 14:34:18 +00:00
const blockifiedFallbackConfig = {
isConversionPossible : ( ) = > false ,
getBlockifiedTemplate : ( ) = > [ ] ,
getDescription : ( ) = > '' ,
2023-05-10 11:02:33 +00:00
onClickCallback : ( ) = > void 0 ,
2023-02-27 14:34:18 +00:00
} ;
const conversionConfig : { [ key : string ] : BlockifiedTemplateConfig } = {
[ TYPES . productCatalog ] : blockifiedProductCatalogConfig ,
[ TYPES . productTaxonomy ] : blockifiedProductTaxonomyConfig ,
[ TYPES . singleProduct ] : blockifiedSingleProduct ,
[ TYPES . productSearchResults ] : blockifiedProductSearchResults ,
2023-06-29 13:41:22 +00:00
[ TYPES . orderConfirmation ] : blockifiedOrderConfirmation ,
2023-02-27 14:34:18 +00:00
fallback : blockifiedFallbackConfig ,
} ;
2023-05-10 11:02:33 +00:00
const pickBlockClientIds = ( blocks : Array < BlockInstance > ) = >
blocks . reduce < Array < string > > ( ( acc , block ) = > {
if ( block . name === 'core/template-part' ) {
return acc ;
}
return [ . . . acc , block . clientId ] ;
} , [ ] ) ;
2023-06-29 13:41:22 +00:00
const ConvertTemplate = ( { blockifyConfig , clientId , attributes } ) = > {
const { getButtonLabel , onClickCallback , getBlockifiedTemplate } =
blockifyConfig ;
const [ isPopoverOpen , setIsPopoverOpen ] = useState ( false ) ;
const { replaceBlock , selectBlock , replaceBlocks } =
useDispatch ( blockEditorStore ) ;
const { getBlocks } = useSelect ( ( sel ) = > {
return {
getBlocks : sel ( blockEditorStore ) . getBlocks ,
} ;
} , [ ] ) ;
const { createInfoNotice } = useDispatch ( noticesStore ) ;
return (
< div className = "wp-block-woocommerce-classic-template__placeholder-migration-button-container" >
< Button
isPrimary
onClick = { ( ) = > {
onClickCallback ( {
clientId ,
getBlocks ,
attributes ,
replaceBlock ,
selectBlock ,
} ) ;
createInfoNotice (
__ (
'Template transformed into blocks!' ,
'woo-gutenberg-products-block'
) ,
{
actions : [
{
label : __ (
'Undo' ,
'woo-gutenberg-products-block'
) ,
onClick : ( ) = > {
const clientIds = pickBlockClientIds (
getBlocks ( )
) ;
replaceBlocks (
clientIds ,
createBlock (
'core/group' ,
{
layout : {
inherit : true ,
type : 'constrained' ,
} ,
} ,
[
createBlock (
'woocommerce/legacy-template' ,
{
template :
attributes . template ,
}
) ,
]
)
) ;
} ,
} ,
] ,
type : 'snackbar' ,
}
) ;
} }
onMouseEnter = { ( ) = > setIsPopoverOpen ( true ) }
onMouseLeave = { ( ) = > setIsPopoverOpen ( false ) }
text = { getButtonLabel ? getButtonLabel ( ) : '' }
>
{ isPopoverOpen && (
< Popover resize = { false } placement = "right-end" >
< div
style = { {
minWidth : '250px' ,
width : '250px' ,
maxWidth : '250px' ,
minHeight : '300px' ,
height : '300px' ,
maxHeight : '300px' ,
cursor : 'pointer' ,
} }
>
< BlockPreview
blocks = { getBlockifiedTemplate ( attributes ) }
viewportWidth = { 1200 }
additionalStyles = { [
{
css : 'body { padding: 20px !important; height: fit-content !important; overflow:hidden}' ,
} ,
] }
/ >
< / div >
< / Popover >
) }
< / Button >
< / div >
) ;
} ;
2022-06-29 07:42:02 +00:00
const Edit = ( {
clientId ,
attributes ,
setAttributes ,
} : BlockEditProps < Attributes > ) = > {
2023-06-29 13:41:22 +00:00
const blockProps = useBlockProps ( ) ;
const { editedPostId } = useSelect ( ( sel ) = > {
2023-05-10 11:02:33 +00:00
return {
editedPostId : sel ( 'core/edit-site' ) . getEditedPostId ( ) ,
} ;
} , [ ] ) ;
const template = useEntityRecord < {
slug : string ;
title : {
rendered? : string ;
row : string ;
} ;
} > ( 'postType' , 'wp_template' , editedPostId ) ;
2022-09-06 09:52:33 +00:00
const templateDetails = getTemplateDetailsBySlug (
attributes . template ,
TEMPLATES
) ;
2023-05-10 11:02:33 +00:00
const templateTitle =
template . record ? . title . rendered ? . toLowerCase ( ) ? ? attributes . template ;
2022-09-06 09:52:33 +00:00
const templatePlaceholder = templateDetails ? . placeholder ? ? 'fallback' ;
2023-02-27 14:34:18 +00:00
const templateType = templateDetails ? . type ? ? 'fallback' ;
2022-06-29 07:42:02 +00:00
useEffect (
( ) = >
setAttributes ( {
template : attributes.template ,
align : attributes.align ? ? 'wide' ,
} ) ,
[ attributes . align , attributes . template , setAttributes ]
) ;
2023-02-27 14:34:18 +00:00
const {
isConversionPossible ,
getDescription ,
2023-06-29 13:41:22 +00:00
getSkeleton ,
blockifyConfig ,
2023-02-27 14:34:18 +00:00
} = conversionConfig [ templateType ] ;
2023-06-29 13:41:22 +00:00
const skeleton = getSkeleton ? (
getSkeleton ( )
) : (
< img
className = "wp-block-woocommerce-classic-template__placeholder-image"
src = { ` ${ WC_BLOCKS_IMAGE_URL } template-placeholders/ ${ templatePlaceholder } .svg ` }
alt = { templateTitle }
/ >
) ;
2023-03-30 12:47:02 +00:00
const canConvert = isConversionPossible ( ) ;
2023-02-27 14:34:18 +00:00
const placeholderDescription = getDescription ( templateTitle , canConvert ) ;
2021-10-29 07:31:50 +00:00
return (
< div { ...blockProps } >
2023-05-10 11:02:33 +00:00
< Placeholder className = "wp-block-woocommerce-classic-template__placeholder" >
2022-03-22 22:34:43 +00:00
< div className = "wp-block-woocommerce-classic-template__placeholder-wireframe" >
2023-06-29 13:41:22 +00:00
{ skeleton }
< / div >
< div className = "wp-block-woocommerce-classic-template__placeholder-copy" >
< div className = "wp-block-woocommerce-classic-template__placeholder-copy__icon-container" >
< span className = "woo-icon" >
< Icon icon = { woo } / > { ' ' }
{ __ (
'WooCommerce' ,
'woo-gutenberg-products-block'
) }
< / span >
< span >
{ __ (
'Classic Template Placeholder' ,
'woo-gutenberg-products-block'
) }
< / span >
2023-05-10 11:02:33 +00:00
< / div >
2023-06-29 13:41:22 +00:00
< p
dangerouslySetInnerHTML = { {
__html : placeholderDescription ,
} }
2021-11-02 14:42:07 +00:00
/ >
2023-06-29 13:41:22 +00:00
< p >
{ __ (
'You cannot edit the content of this block. However, you can move it and place other blocks around it.' ,
'woo-gutenberg-products-block'
) }
< / p >
{ canConvert && blockifyConfig && (
< ConvertTemplate
clientId = { clientId }
blockifyConfig = { blockifyConfig }
attributes = { attributes }
/ >
) }
2021-11-02 14:42:07 +00:00
< / div >
< / Placeholder >
2021-10-29 07:31:50 +00:00
< / div >
) ;
} ;
2022-06-29 07:42:02 +00:00
const registerClassicTemplateBlock = ( {
template ,
inserter ,
} : {
template? : string ;
inserter : boolean ;
} ) = > {
/ * *
* The 'WooCommerce Legacy Template' block was renamed to 'WooCommerce Classic Template' , however , the internal block
* name 'woocommerce/legacy-template' needs to remain the same . Otherwise , it would result in a corrupt block when
* loaded for users who have customized templates using the legacy - template ( since the internal block name is
* stored in the database ) .
*
* See https : //github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5861 for more context
* /
registerBlockType ( BLOCK_SLUG , {
2022-09-06 09:52:33 +00:00
title :
template && TEMPLATES [ template ]
? TEMPLATES [ template ] . title
: __ (
'WooCommerce Classic Template' ,
'woo-gutenberg-products-block'
) ,
2022-06-29 07:42:02 +00:00
icon : (
< Icon
icon = { box }
className = "wc-block-editor-components-block-icon"
/ >
) ,
category : 'woocommerce' ,
apiVersion : 2 ,
keywords : [ __ ( 'WooCommerce' , 'woo-gutenberg-products-block' ) ] ,
description : __ (
'Renders classic WooCommerce PHP templates.' ,
'woo-gutenberg-products-block'
) ,
supports : {
align : [ 'wide' , 'full' ] ,
html : false ,
multiple : false ,
reusable : false ,
inserter ,
} ,
attributes : {
/ * *
* Template attribute is used to determine which core PHP template gets rendered .
* /
template : {
type : 'string' ,
default : 'any' ,
} ,
align : {
type : 'string' ,
default : 'wide' ,
} ,
2022-01-27 09:45:00 +00:00
} ,
2022-06-29 07:42:02 +00:00
edit : ( {
attributes ,
clientId ,
setAttributes ,
} : BlockEditProps < Attributes > ) = > {
const newTemplate = template ? ? attributes . template ;
return (
< Edit
attributes = { {
. . . attributes ,
template : newTemplate ,
} }
setAttributes = { setAttributes }
clientId = { clientId }
/ >
) ;
2022-05-04 11:02:42 +00:00
} ,
2022-06-29 07:42:02 +00:00
save : ( ) = > null ,
} ) ;
} ;
2023-06-12 16:55:20 +00:00
/ * *
* Attempts to recover the Classic Template block if it fails to render on the Single Product template
* due to the user resetting customizations without refreshing the page .
*
* When the Classic Template block fails to render , it is replaced by the 'core/missing' block , which
* displays an error message stating that the WooCommerce Classic template block is unsupported .
*
* This function replaces the 'core/missing' block with the original Classic Template block that failed
* to render , allowing the block to be displayed correctly .
*
* @see { @link https : //github.com/woocommerce/woocommerce-blocks/issues/9637|Issue: Block error is displayed on clearing customizations for Woo Templates}
*
* /
const tryToRecoverClassicTemplateBlockWhenItFailsToRender = debounce ( ( ) = > {
const blocks = select ( 'core/block-editor' ) . getBlocks ( ) ;
const blocksIncludingInnerBlocks = blocks . flatMap ( ( block ) = > [
block ,
. . . block . innerBlocks ,
] ) ;
const classicTemplateThatFailedToRender = blocksIncludingInnerBlocks . find (
( block ) = >
block . name === 'core/missing' &&
block . attributes . originalName === BLOCK_SLUG
) ;
if ( classicTemplateThatFailedToRender ) {
const blockToReplaceClassicTemplateBlockThatFailedToRender = parse (
classicTemplateThatFailedToRender . attributes . originalContent
) ;
if ( blockToReplaceClassicTemplateBlockThatFailedToRender ) {
dispatch ( 'core/block-editor' ) . replaceBlock (
classicTemplateThatFailedToRender . clientId ,
blockToReplaceClassicTemplateBlockThatFailedToRender
) ;
}
}
} , 100 ) ;
2022-06-29 07:42:02 +00:00
// @todo Refactor when there will be possible to show a block according on a template/post with a Gutenberg API. https://github.com/WordPress/gutenberg/pull/41718
let currentTemplateId : string | undefined ;
2023-05-25 07:42:29 +00:00
subscribe ( ( ) = > {
const previousTemplateId = currentTemplateId ;
const store = select ( 'core/edit-site' ) ;
2023-08-04 14:16:19 +00:00
// With GB 16.3.0 the return type can be a number: https://github.com/WordPress/gutenberg/issues/53230
const editedPostId = store ? . getEditedPostId ( ) as
| string
| number
| undefined ;
currentTemplateId = isNumber ( editedPostId ) ? undefined : editedPostId ;
2023-05-25 07:42:29 +00:00
const parsedTemplate = currentTemplateId ? . split ( '//' ) [ 1 ] ;
2022-06-29 07:42:02 +00:00
2023-05-25 07:42:29 +00:00
if ( parsedTemplate === null || parsedTemplate === undefined ) {
return ;
}
2022-06-29 07:42:02 +00:00
2023-05-25 07:42:29 +00:00
const block = getBlockType ( BLOCK_SLUG ) ;
2023-06-12 16:55:20 +00:00
const isBlockRegistered = Boolean ( block ) ;
if (
isBlockRegistered &&
hasTemplateSupportForClassicTemplateBlock ( parsedTemplate , TEMPLATES )
) {
tryToRecoverClassicTemplateBlockWhenItFailsToRender ( ) ;
}
if ( previousTemplateId === currentTemplateId ) {
return ;
}
2022-06-29 07:42:02 +00:00
2023-05-25 07:42:29 +00:00
if (
2023-06-12 16:55:20 +00:00
isBlockRegistered &&
2023-05-25 07:42:29 +00:00
( ! hasTemplateSupportForClassicTemplateBlock (
parsedTemplate ,
TEMPLATES
) ||
isClassicTemplateBlockRegisteredWithAnotherTitle (
block ,
parsedTemplate
) )
) {
unregisterBlockType ( BLOCK_SLUG ) ;
currentTemplateId = undefined ;
return ;
}
2022-06-29 07:42:02 +00:00
2023-05-25 07:42:29 +00:00
if (
2023-06-12 16:55:20 +00:00
! isBlockRegistered &&
2023-05-25 07:42:29 +00:00
hasTemplateSupportForClassicTemplateBlock ( parsedTemplate , TEMPLATES )
) {
registerClassicTemplateBlock ( {
template : parsedTemplate ,
inserter : true ,
} ) ;
}
2023-06-12 16:55:20 +00:00
} , 'core/blocks-editor' ) ;