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 ,
} 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' ;
2024-06-26 10:20:22 +00:00
import { useDispatch , subscribe , useSelect , select } 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-29 13:41:22 +00:00
import { woo } from '@woocommerce/icons' ;
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
2023-12-08 23:30:33 +00:00
variant = "primary"
2023-06-29 13:41:22 +00:00
onClick = { ( ) = > {
onClickCallback ( {
clientId ,
getBlocks ,
attributes ,
replaceBlock ,
selectBlock ,
} ) ;
createInfoNotice (
__ (
'Template transformed into blocks!' ,
2023-12-12 22:12:36 +00:00
'woocommerce'
2023-06-29 13:41:22 +00:00
) ,
{
actions : [
{
2023-12-12 23:05:20 +00:00
label : __ ( 'Undo' , 'woocommerce' ) ,
2023-06-29 13:41:22 +00:00
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
2023-10-19 15:43:43 +00:00
blocks = { getBlockifiedTemplate ( {
. . . attributes ,
isPreview : true ,
} ) }
2023-06-29 13:41:22 +00:00
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 } / > { ' ' }
2023-12-12 23:05:20 +00:00
{ __ ( 'WooCommerce' , 'woocommerce' ) }
2023-06-29 13:41:22 +00:00
< / span >
< span >
{ __ (
'Classic Template Placeholder' ,
2023-12-12 22:12:36 +00:00
'woocommerce'
2023-06-29 13:41:22 +00:00
) }
< / 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.' ,
2023-12-12 22:12:36 +00:00
'woocommerce'
2023-06-29 13:41:22 +00:00
) }
< / 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 ,
} : {
2024-06-26 10:20:22 +00:00
template? : string | null ;
2022-06-29 07:42:02 +00:00
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
2023-12-12 23:05:20 +00:00
: __ ( 'WooCommerce Classic Template' , 'woocommerce' ) ,
2022-06-29 07:42:02 +00:00
icon : (
< Icon
icon = { box }
className = "wc-block-editor-components-block-icon"
/ >
) ,
category : 'woocommerce' ,
apiVersion : 2 ,
2023-12-12 22:12:36 +00:00
keywords : [ __ ( 'WooCommerce' , 'woocommerce' ) ] ,
2024-05-22 08:53:43 +00:00
description :
template && TEMPLATES [ template ]
? TEMPLATES [ template ] . description
: __ (
'Renders classic WooCommerce PHP templates.' ,
'woocommerce'
) ,
2022-06-29 07:42:02 +00:00
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 ,
} ) ;
} ;
// @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
2024-06-26 10:20:22 +00:00
let previousEditedTemplate : string | number | null = null ;
let isBlockRegistered = false ;
let isBlockInInserter = false ;
const handleRegisterClassicTemplateBlock = ( {
template ,
inserter ,
} : {
template : string | null ;
inserter : boolean ;
} ) = > {
if ( isBlockRegistered ) {
unregisterBlockType ( BLOCK_SLUG ) ;
}
isBlockInInserter = inserter ;
isBlockRegistered = true ;
registerClassicTemplateBlock ( {
template ,
inserter ,
} ) ;
} ;
2022-06-29 07:42:02 +00:00
2023-05-25 07:42:29 +00:00
subscribe ( ( ) = > {
2024-06-26 10:20:22 +00:00
const editorStore = select ( 'core/editor' ) ;
// We use blockCount to know if we are editing a template or in the navigation.
const blockCount = editorStore ? . getBlockCount ( ) as number ;
const templateSlug = editorStore ? . getEditedPostSlug ( ) as string | null ;
const editedTemplate = blockCount && blockCount > 0 ? templateSlug : null ;
// Skip if we are in the same template, except if the block hasn't been registered yet.
if ( isBlockRegistered && previousEditedTemplate === editedTemplate ) {
2023-05-25 07:42:29 +00:00
return ;
}
2024-06-26 10:20:22 +00:00
previousEditedTemplate = editedTemplate ;
// Handle the case when we are not editing a template (ie: in the navigation screen).
if ( ! editedTemplate ) {
if ( ! isBlockRegistered ) {
handleRegisterClassicTemplateBlock ( {
template : editedTemplate ,
inserter : false ,
} ) ;
}
return ;
2023-06-12 16:55:20 +00:00
}
2024-06-26 10:20:22 +00:00
const templateSupportsClassicTemplateBlock =
hasTemplateSupportForClassicTemplateBlock ( editedTemplate , TEMPLATES ) ;
// Handle the case when we are editing a template that doesn't support the Classic Template block (ie: Blog Home).
if ( ! templateSupportsClassicTemplateBlock && isBlockInInserter ) {
handleRegisterClassicTemplateBlock ( {
template : editedTemplate ,
inserter : false ,
} ) ;
2023-06-12 16:55:20 +00:00
return ;
}
2022-06-29 07:42:02 +00:00
2024-06-26 10:20:22 +00:00
// Handle the case when we are editing a template that does support the Classic Template block (ie: Product Catalog).
if ( templateSupportsClassicTemplateBlock && ! isBlockInInserter ) {
handleRegisterClassicTemplateBlock ( {
template : editedTemplate ,
inserter : true ,
} ) ;
2023-05-25 07:42:29 +00:00
return ;
}
2022-06-29 07:42:02 +00:00
2024-06-26 10:20:22 +00:00
// Handle the case when we are editing a template that does support the Classic Template block but it's currently registered with another title (ie: navigating from the Product Catalog template to the Product Search Results template).
2023-05-25 07:42:29 +00:00
if (
2024-06-26 10:20:22 +00:00
templateSupportsClassicTemplateBlock &&
isClassicTemplateBlockRegisteredWithAnotherTitle (
getBlockType ( BLOCK_SLUG ) ,
editedTemplate
)
2023-05-25 07:42:29 +00:00
) {
2024-06-26 10:20:22 +00:00
handleRegisterClassicTemplateBlock ( {
template : editedTemplate ,
2023-05-25 07:42:29 +00:00
inserter : true ,
} ) ;
}
2023-06-12 16:55:20 +00:00
} , 'core/blocks-editor' ) ;