Introduce a product type selection within the new experience (#41823)
* Create a relation between the product type and the product block template * Add 'patterns' to name the kind of products that can be created for a specific template * Resolve template using its id as a template query param * Rename ProductEditPattern to ProductTemplate * Rename get_patterns hook to woocommerce_product_editor_get_product_templates * Return the list of templates to the client * Set layout template events as array * Register the layout template based on the product template or the post type in case of product variations * Registering non supported product types * Create and register the woocommerce/product-details-section-description block * Add the product type to the section description * Create product type selector * Fix menu item style * Highlight selected menu item * Set the selected product template * Set product template title to lowercase in the content description * Rename blocks by blockTemplates under the AbstractBlockTemplate class * Rename to woocommerce_product_editor_product_templates filter * Remove product_template_ prefix from the supported_product_types map * Rename get_formatted to to_JSON and convert the props to client side like * Refactor get_product_templates * Fix icon resolution * Add a confirmation modal for unsupported product templates * Add changelog files * Remove product types using for testing * Fix redirection when changing to a non supported product template * Set the change button state to busy when it is saving the product * Fix php linter errors * Fix rebase conflict * Move ProductTemplate to Automattic\WooCommerce\Admin\Features\ProductBlockEditor namespace * Add the to_json definition to the BlockTemplateInterface * Create default product template by custom product type if it does not have a template associated yet * Fix some comments and product template creation validation * Add support to load the product template icon from an external resource * Fix php linter * Fix the changelog description
This commit is contained in:
parent
eac1a460f0
commit
a592a473d3
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Introduce a product type selection within the new experience
|
|
@ -25,6 +25,7 @@ export { init as initToggle } from './generic/toggle';
|
|||
export { init as attributesInit } from './product-fields/attributes';
|
||||
export { init as initVariations } from './product-fields/variations';
|
||||
export { init as initRequirePassword } from './product-fields/password';
|
||||
export { init as initProductDetailsSectionDescription } from './product-fields/product-details-section-description';
|
||||
export { init as initProductList } from './product-fields/product-list';
|
||||
export { init as initVariationItems } from './product-fields/variation-items';
|
||||
export { init as initVariationOptions } from './product-fields/variation-options';
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 2,
|
||||
"name": "woocommerce/product-details-section-description",
|
||||
"title": "Product details section description",
|
||||
"category": "woocommerce",
|
||||
"description": "The product details section description.",
|
||||
"keywords": [ "products", "section", "description" ],
|
||||
"textdomain": "default",
|
||||
"attributes": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"__experimentalRole": "content"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": true,
|
||||
"reusable": false,
|
||||
"inserter": false,
|
||||
"lock": false,
|
||||
"__experimentalToolbar": false
|
||||
},
|
||||
"editorStyle": "file:./editor.css"
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
MenuGroup,
|
||||
MenuItem,
|
||||
Modal,
|
||||
} from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import {
|
||||
createElement,
|
||||
createInterpolateElement,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import * as icons from '@wordpress/icons';
|
||||
import { useWooBlockProps } from '@woocommerce/block-templates';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { getNewPath } from '@woocommerce/navigation';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { useEntityId } from '@wordpress/core-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductEditorSettings } from '../../../components';
|
||||
import { ProductTemplate } from '../../../components/editor';
|
||||
import { BlockFill } from '../../../components/block-slot-fill';
|
||||
import { useValidations } from '../../../contexts/validation-context';
|
||||
import {
|
||||
WPError,
|
||||
getProductErrorMessage,
|
||||
} from '../../../utils/get-product-error-message';
|
||||
import { ProductEditorBlockEditProps } from '../../../types';
|
||||
import { ProductDetailsSectionDescriptionBlockAttributes } from './types';
|
||||
|
||||
export function ProductDetailsSectionDescriptionBlockEdit( {
|
||||
attributes,
|
||||
clientId,
|
||||
}: ProductEditorBlockEditProps< ProductDetailsSectionDescriptionBlockAttributes > ) {
|
||||
const blockProps = useWooBlockProps( attributes );
|
||||
|
||||
const { productTemplates, productTemplate: selectedProductTemplate } =
|
||||
useSelect( ( select ) => {
|
||||
const { getEditorSettings } = select( 'core/editor' );
|
||||
return getEditorSettings() as ProductEditorSettings;
|
||||
} );
|
||||
|
||||
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
|
||||
const [ supportedProductTemplates, unsupportedProductTemplates ] =
|
||||
productTemplates.reduce< [ ProductTemplate[], ProductTemplate[] ] >(
|
||||
( [ supported, unsupported ], productTemplate ) => {
|
||||
if ( productTemplate.layoutTemplateId ) {
|
||||
supported.push( productTemplate );
|
||||
} else {
|
||||
unsupported.push( productTemplate );
|
||||
}
|
||||
return [ supported, unsupported ];
|
||||
},
|
||||
[ [], [] ]
|
||||
);
|
||||
|
||||
const productId = useEntityId( 'postType', 'product' );
|
||||
|
||||
const { validate } = useValidations< Product >();
|
||||
// @ts-expect-error There are no types for this.
|
||||
const { editEntityRecord, saveEditedEntityRecord, saveEntityRecord } =
|
||||
useDispatch( 'core' );
|
||||
const { createSuccessNotice, createErrorNotice } =
|
||||
useDispatch( 'core/notices' );
|
||||
|
||||
const rootClientId = useSelect(
|
||||
( select ) => {
|
||||
const { getBlockRootClientId } = select( 'core/block-editor' );
|
||||
return getBlockRootClientId( clientId );
|
||||
},
|
||||
[ clientId ]
|
||||
);
|
||||
|
||||
const [ unsupportedProductTemplate, setUnsupportedProductTemplate ] =
|
||||
useState< ProductTemplate >();
|
||||
|
||||
const { isSaving } = useSelect(
|
||||
( select ) => {
|
||||
// @ts-expect-error There are no types for this.
|
||||
const { isSavingEntityRecord } = select( 'core' );
|
||||
|
||||
return {
|
||||
isSaving: isSavingEntityRecord< boolean >(
|
||||
'postType',
|
||||
'product',
|
||||
productId
|
||||
),
|
||||
};
|
||||
},
|
||||
[ productId ]
|
||||
);
|
||||
|
||||
if ( ! rootClientId ) return;
|
||||
|
||||
function menuItemClickHandler(
|
||||
productTemplate: ProductTemplate,
|
||||
onClose: () => void
|
||||
) {
|
||||
return async function handleMenuItemClick() {
|
||||
try {
|
||||
if ( ! productTemplate.layoutTemplateId ) {
|
||||
setUnsupportedProductTemplate( productTemplate );
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
await validate( productTemplate.productData );
|
||||
|
||||
await editEntityRecord(
|
||||
'postType',
|
||||
'product',
|
||||
productId,
|
||||
productTemplate.productData
|
||||
);
|
||||
|
||||
await saveEditedEntityRecord< Product >(
|
||||
'postType',
|
||||
'product',
|
||||
productId,
|
||||
{
|
||||
throwOnError: true,
|
||||
}
|
||||
);
|
||||
|
||||
createSuccessNotice(
|
||||
__( 'Product type changed.', 'woocommerce' )
|
||||
);
|
||||
} catch ( error ) {
|
||||
const message = getProductErrorMessage( error as WPError );
|
||||
createErrorNotice( message );
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
}
|
||||
|
||||
function resolveIcon( iconId?: string | null, alt?: string ) {
|
||||
if ( ! iconId ) return undefined;
|
||||
|
||||
const { Icon } = icons;
|
||||
let icon: JSX.Element;
|
||||
|
||||
if ( /^https?:\/\//.test( iconId ) ) {
|
||||
icon = <img src={ iconId } alt={ alt } />;
|
||||
} else {
|
||||
if ( ! ( iconId in icons ) ) return undefined;
|
||||
|
||||
icon = icons[ iconId as never ];
|
||||
}
|
||||
|
||||
return <Icon icon={ icon } size={ 24 } />;
|
||||
}
|
||||
|
||||
function getMenuItem( onClose: () => void ) {
|
||||
return function renderMenuItem( productTemplate: ProductTemplate ) {
|
||||
const isSelected =
|
||||
selectedProductTemplate?.id === productTemplate.id;
|
||||
return (
|
||||
<MenuItem
|
||||
key={ productTemplate.id }
|
||||
info={ productTemplate.description ?? undefined }
|
||||
isSelected={ isSelected }
|
||||
icon={
|
||||
isSelected
|
||||
? resolveIcon( 'check' )
|
||||
: resolveIcon(
|
||||
productTemplate.icon,
|
||||
productTemplate.title
|
||||
)
|
||||
}
|
||||
iconPosition="left"
|
||||
role="menuitemradio"
|
||||
onClick={ menuItemClickHandler( productTemplate, onClose ) }
|
||||
className={ classNames( {
|
||||
'components-menu-item__button--selected': isSelected,
|
||||
} ) }
|
||||
>
|
||||
{ productTemplate.title }
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
async function handleModelChangeClick() {
|
||||
try {
|
||||
if ( isSaving ) return;
|
||||
|
||||
await validate( unsupportedProductTemplate?.productData );
|
||||
|
||||
const product = ( await saveEditedEntityRecord< Product >(
|
||||
'postType',
|
||||
'product',
|
||||
productId,
|
||||
{
|
||||
throwOnError: true,
|
||||
}
|
||||
) ) ?? { id: productId };
|
||||
|
||||
// Avoiding to save some changes that are not supported by the current product template.
|
||||
// So in this case those changes are saved directly to the server.
|
||||
await saveEntityRecord(
|
||||
'postType',
|
||||
'product',
|
||||
{
|
||||
...product,
|
||||
...unsupportedProductTemplate?.productData,
|
||||
},
|
||||
// @ts-expect-error Expected 3 arguments, but got 4.
|
||||
{
|
||||
throwOnError: true,
|
||||
}
|
||||
);
|
||||
|
||||
createSuccessNotice( __( 'Product type changed.', 'woocommerce' ) );
|
||||
|
||||
// Let the server manage the redirection when the product is not supported
|
||||
// by the product editor.
|
||||
window.location.href = getNewPath( {}, `/product/${ productId }` );
|
||||
} catch ( error ) {
|
||||
const message = getProductErrorMessage( error as WPError );
|
||||
createErrorNotice( message );
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<BlockFill
|
||||
name="section-description"
|
||||
clientId={ clientId }
|
||||
slotContainerBlockName="woocommerce/product-section"
|
||||
>
|
||||
<div { ...blockProps }>
|
||||
<p>
|
||||
{ createInterpolateElement(
|
||||
/* translators: <ProductTemplate />: the product template. */
|
||||
__( 'This is a <ProductTemplate />.', 'woocommerce' ),
|
||||
{
|
||||
ProductTemplate: (
|
||||
<span>
|
||||
{ selectedProductTemplate?.title?.toLowerCase() }
|
||||
</span>
|
||||
),
|
||||
}
|
||||
) }
|
||||
</p>
|
||||
|
||||
<Dropdown
|
||||
focusOnMount={ false }
|
||||
// @ts-expect-error Property does exists
|
||||
popoverProps={ {
|
||||
placement: 'bottom-start',
|
||||
} }
|
||||
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||
<Button
|
||||
aria-expanded={ isOpen }
|
||||
variant="link"
|
||||
onClick={ onToggle }
|
||||
>
|
||||
<span>
|
||||
{ __( 'Change product type', 'woocommerce' ) }
|
||||
</span>
|
||||
</Button>
|
||||
) }
|
||||
renderContent={ ( { onClose } ) => (
|
||||
<div className="wp-block-woocommerce-product-details-section-description__dropdown components-dropdown-menu__menu">
|
||||
<MenuGroup>
|
||||
{ supportedProductTemplates.map(
|
||||
getMenuItem( onClose )
|
||||
) }
|
||||
</MenuGroup>
|
||||
|
||||
{ unsupportedProductTemplates.length > 0 && (
|
||||
<MenuGroup>
|
||||
<Dropdown
|
||||
focusOnMount={ false }
|
||||
// @ts-expect-error Property does exists
|
||||
popoverProps={ {
|
||||
placement: 'right-start',
|
||||
} }
|
||||
renderToggle={ ( {
|
||||
isOpen,
|
||||
onToggle,
|
||||
} ) => (
|
||||
<MenuItem
|
||||
aria-expanded={ isOpen }
|
||||
icon={ resolveIcon(
|
||||
'chevronRight'
|
||||
) }
|
||||
iconPosition="right"
|
||||
onClick={ onToggle }
|
||||
>
|
||||
<span>
|
||||
{ __(
|
||||
'More',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
</MenuItem>
|
||||
) }
|
||||
renderContent={ () => (
|
||||
<div className="wp-block-woocommerce-product-details-section-description__dropdown components-dropdown-menu__menu">
|
||||
<MenuGroup>
|
||||
{ unsupportedProductTemplates.map(
|
||||
getMenuItem( onClose )
|
||||
) }
|
||||
</MenuGroup>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
</MenuGroup>
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
|
||||
{ Boolean( unsupportedProductTemplate ) && (
|
||||
<Modal
|
||||
title={ __( 'Change product type?', 'woocommerce' ) }
|
||||
className="wp-block-woocommerce-product-details-section-description__modal"
|
||||
onRequestClose={ () => {
|
||||
setUnsupportedProductTemplate( undefined );
|
||||
} }
|
||||
>
|
||||
<p>
|
||||
<b>
|
||||
{ __(
|
||||
'This product type isn’t supported by the updated product editing experience yet.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{ __(
|
||||
'You’ll be taken to the classic editing screen that isn’t optimized for commerce but offers advanced functionality and supports all extensions.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</p>
|
||||
|
||||
<div className="wp-block-woocommerce-product-details-section-description__modal-actions">
|
||||
<Button
|
||||
variant="secondary"
|
||||
aria-disabled={ isSaving }
|
||||
onClick={ () => {
|
||||
if ( isSaving ) return;
|
||||
setUnsupportedProductTemplate( undefined );
|
||||
} }
|
||||
>
|
||||
{ __( 'Cancel', 'woocommerce' ) }
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
isBusy={ isSaving }
|
||||
aria-disabled={ isSaving }
|
||||
onClick={ handleModelChangeClick }
|
||||
>
|
||||
{ __( 'Change', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
) }
|
||||
</div>
|
||||
</BlockFill>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
.wp-block-woocommerce-product-details-section-description {
|
||||
margin-top: $grid-unit;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: $grid-unit-05;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__dropdown .components-button.components-menu-item__button {
|
||||
border-radius: 2px;
|
||||
padding: $grid-unit $grid-unit + 2;
|
||||
padding-inline-start: $grid-unit-30 + $grid-unit-05;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-100;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&.has-icon {
|
||||
gap: $grid-unit-05;
|
||||
padding-inline-start: 0;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
&--selected {
|
||||
svg,
|
||||
.components-menu-item__item {
|
||||
color: var(--wp-admin-theme-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__modal {
|
||||
max-width: 650px;
|
||||
|
||||
&-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: $grid-unit;
|
||||
padding-top: $grid-unit-40;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { registerProductEditorBlockType } from '../../../utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import blockConfiguration from './block.json';
|
||||
import { ProductDetailsSectionDescriptionBlockEdit } from './edit';
|
||||
|
||||
const { name, ...metadata } = blockConfiguration;
|
||||
|
||||
export { metadata, name };
|
||||
|
||||
export const settings = {
|
||||
example: {},
|
||||
edit: ProductDetailsSectionDescriptionBlockEdit,
|
||||
};
|
||||
|
||||
export function init() {
|
||||
return registerProductEditorBlockType( {
|
||||
name,
|
||||
metadata: metadata as never,
|
||||
settings: settings as never,
|
||||
} );
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { BlockAttributes } from '@wordpress/blocks';
|
||||
|
||||
export interface ProductDetailsSectionDescriptionBlockAttributes
|
||||
extends BlockAttributes {
|
||||
content: string;
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
@import "product-fields/variations/editor.scss";
|
||||
@import "product-fields/password/editor.scss";
|
||||
@import "product-fields/product-list/editor.scss";
|
||||
@import "product-fields/product-details-section-description/editor.scss";
|
||||
@import "product-fields/variation-items/editor.scss";
|
||||
@import "product-fields/variation-options/editor.scss";
|
||||
@import "generic/taxonomy/editor.scss";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { synchronizeBlocksWithTemplate, Template } from '@wordpress/blocks';
|
||||
import { synchronizeBlocksWithTemplate } from '@wordpress/blocks';
|
||||
import {
|
||||
createElement,
|
||||
useMemo,
|
||||
|
@ -22,8 +22,6 @@ import {
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
BlockTools,
|
||||
EditorSettings,
|
||||
EditorBlockListSettings,
|
||||
ObserveTyping,
|
||||
} from '@wordpress/block-editor';
|
||||
// It doesn't seem to notice the External dependency block whn @ts-ignore is added.
|
||||
|
@ -32,37 +30,26 @@ import {
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore store should be included.
|
||||
useEntityBlockEditor,
|
||||
useEntityProp,
|
||||
} from '@wordpress/core-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useConfirmUnsavedProductChanges } from '../../hooks/use-confirm-unsaved-product-changes';
|
||||
import { ProductEditorContext } from '../../types';
|
||||
import { PostTypeContext } from '../../contexts/post-type-context';
|
||||
import { ModalEditor } from '../modal-editor';
|
||||
import { store as productEditorUiStore } from '../../store/product-editor-ui';
|
||||
|
||||
type BlockEditorSettings = Partial<
|
||||
EditorSettings & EditorBlockListSettings
|
||||
> & {
|
||||
templates?: Record< string, Template[] >;
|
||||
};
|
||||
|
||||
type BlockEditorProps = {
|
||||
context: Partial< ProductEditorContext >;
|
||||
productType: string;
|
||||
productId: number;
|
||||
settings: BlockEditorSettings | undefined;
|
||||
};
|
||||
import { ModalEditor } from '../modal-editor';
|
||||
import { ProductEditorSettings } from '../editor';
|
||||
import { BlockEditorProps } from './types';
|
||||
|
||||
export function BlockEditor( {
|
||||
context,
|
||||
settings: _settings,
|
||||
productType,
|
||||
postType,
|
||||
productId,
|
||||
}: BlockEditorProps ) {
|
||||
useConfirmUnsavedProductChanges( productType );
|
||||
useConfirmUnsavedProductChanges( postType );
|
||||
|
||||
const canUserCreateMedia = useSelect( ( select: typeof WPSelect ) => {
|
||||
const { canUser } = select( 'core' );
|
||||
|
@ -82,7 +69,7 @@ export function BlockEditor( {
|
|||
return () => window.removeEventListener( 'scroll', wpPinMenuEvent );
|
||||
}, [] );
|
||||
|
||||
const settings: BlockEditorSettings = useMemo( () => {
|
||||
const settings = useMemo< Partial< ProductEditorSettings > >( () => {
|
||||
const mediaSettings = canUserCreateMedia
|
||||
? {
|
||||
mediaUpload( {
|
||||
|
@ -110,27 +97,51 @@ export function BlockEditor( {
|
|||
};
|
||||
}, [ canUserCreateMedia, _settings ] );
|
||||
|
||||
const [ productType ] = useEntityProp( 'postType', postType, 'type' );
|
||||
|
||||
const [ blocks, onInput, onChange ] = useEntityBlockEditor(
|
||||
'postType',
|
||||
productType,
|
||||
postType,
|
||||
{ id: productId }
|
||||
);
|
||||
|
||||
const { updateEditorSettings } = useDispatch( 'core/editor' );
|
||||
|
||||
useLayoutEffect( () => {
|
||||
const template = settings?.templates?.[ productType ];
|
||||
const productTemplates = settings?.productTemplates ?? [];
|
||||
const productTemplate = productTemplates.find(
|
||||
( template ) => template.productData.type === productType
|
||||
);
|
||||
|
||||
if ( ! template ) {
|
||||
const layoutTemplates = settings?.layoutTemplates ?? [];
|
||||
|
||||
let layoutTemplateId = productTemplate?.layoutTemplateId;
|
||||
// Product variations do not have a related product template but
|
||||
// they do have a layout template
|
||||
if ( postType === 'product_variation' ) {
|
||||
layoutTemplateId = 'product-variation';
|
||||
}
|
||||
|
||||
const layoutTemplate = layoutTemplates.find(
|
||||
( template ) => template.id === layoutTemplateId
|
||||
);
|
||||
|
||||
if ( ! layoutTemplate ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blockInstances = synchronizeBlocksWithTemplate( [], template );
|
||||
const blockInstances = synchronizeBlocksWithTemplate(
|
||||
[],
|
||||
layoutTemplate.blockTemplates
|
||||
);
|
||||
|
||||
onChange( blockInstances, {} );
|
||||
|
||||
updateEditorSettings( settings ?? {} );
|
||||
}, [ productType, productId ] );
|
||||
updateEditorSettings( {
|
||||
...settings,
|
||||
productTemplate,
|
||||
} as Partial< ProductEditorSettings > );
|
||||
}, [ settings, postType, productType ] );
|
||||
|
||||
// Check if the Modal editor is open from the store.
|
||||
const isModalEditorOpen = useSelect( ( select ) => {
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './block-editor';
|
||||
export * from './types';
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductEditorContext } from '../../types';
|
||||
import { ProductEditorSettings } from '../editor';
|
||||
|
||||
export type BlockEditorProps = {
|
||||
context: Partial< ProductEditorContext >;
|
||||
postType: string;
|
||||
productId: number;
|
||||
settings?: ProductEditorSettings;
|
||||
};
|
|
@ -11,13 +11,7 @@ import {
|
|||
LayoutContextProvider,
|
||||
useExtendLayout,
|
||||
} from '@woocommerce/admin-layout';
|
||||
import {
|
||||
EditorSettings,
|
||||
EditorBlockListSettings,
|
||||
} from '@wordpress/block-editor';
|
||||
import { Template } from '@wordpress/blocks';
|
||||
import { Popover } from '@wordpress/components';
|
||||
import { Product } from '@woocommerce/data';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
|
@ -37,18 +31,7 @@ import { InterfaceSkeleton } from '@wordpress/interface';
|
|||
import { Header } from '../header';
|
||||
import { BlockEditor } from '../block-editor';
|
||||
import { ValidationProvider } from '../../contexts/validation-context';
|
||||
|
||||
export type ProductEditorSettings = Partial<
|
||||
EditorSettings & EditorBlockListSettings
|
||||
> & {
|
||||
templates: Record< string, Template[] >;
|
||||
};
|
||||
|
||||
type EditorProps = {
|
||||
product: Pick< Product, 'id' | 'type' >;
|
||||
productType?: string;
|
||||
settings: ProductEditorSettings | undefined;
|
||||
};
|
||||
import { EditorProps } from './types';
|
||||
|
||||
export function Editor( {
|
||||
product,
|
||||
|
@ -80,7 +63,7 @@ export function Editor( {
|
|||
<>
|
||||
<BlockEditor
|
||||
settings={ settings }
|
||||
productType={ productType }
|
||||
postType={ productType }
|
||||
productId={ product.id }
|
||||
context={ {
|
||||
selectedTab,
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './editor';
|
||||
export * from './init-blocks';
|
||||
export * from './types';
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Product } from '@woocommerce/data';
|
||||
import {
|
||||
EditorSettings,
|
||||
EditorBlockListSettings,
|
||||
} from '@wordpress/block-editor';
|
||||
import { Template } from '@wordpress/blocks';
|
||||
|
||||
export type LayoutTemplate = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
area: string;
|
||||
blockTemplates: Template[];
|
||||
};
|
||||
|
||||
export type ProductTemplate = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
icon: string | null;
|
||||
order: number;
|
||||
layoutTemplateId: string;
|
||||
productData: Partial< Product >;
|
||||
};
|
||||
|
||||
export type ProductEditorSettings = Partial<
|
||||
EditorSettings & EditorBlockListSettings
|
||||
> & {
|
||||
layoutTemplates: LayoutTemplate[];
|
||||
productTemplates: ProductTemplate[];
|
||||
productTemplate?: ProductTemplate;
|
||||
};
|
||||
|
||||
export type EditorProps = {
|
||||
product: Pick< Product, 'id' | 'type' >;
|
||||
productType?: string;
|
||||
settings?: ProductEditorSettings;
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Introduce a product type selection within the new product editor experience
|
|
@ -33,4 +33,11 @@ interface BlockTemplateInterface extends ContainerInterface {
|
|||
* @return string
|
||||
*/
|
||||
public function generate_block_id( string $id_base ): string;
|
||||
|
||||
/**
|
||||
* Get the template as JSON like array.
|
||||
*
|
||||
* @return array The JSON.
|
||||
*/
|
||||
public function to_json(): array;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ class BlockRegistry {
|
|||
'woocommerce/product-pricing-field',
|
||||
'woocommerce/product-section',
|
||||
'woocommerce/product-section-description',
|
||||
'woocommerce/product-details-section-description',
|
||||
'woocommerce/product-tab',
|
||||
'woocommerce/product-toggle-field',
|
||||
'woocommerce/product-taxonomy-field',
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
|
|||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates\SimpleProductTemplate;
|
||||
use Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates\ProductVariationTemplate;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplate;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplateRegistry\BlockTemplateRegistry;
|
||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\Block;
|
||||
|
@ -24,11 +25,11 @@ class Init {
|
|||
const EDITOR_CONTEXT_NAME = 'woocommerce/edit-product';
|
||||
|
||||
/**
|
||||
* Supported post types.
|
||||
* Supported product types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $supported_post_types = array( 'simple' );
|
||||
private $supported_product_types = array( 'simple' );
|
||||
|
||||
/**
|
||||
* Redirection controller.
|
||||
|
@ -42,18 +43,18 @@ class Init {
|
|||
*/
|
||||
public function __construct() {
|
||||
if ( Features::is_enabled( 'product-variation-management' ) ) {
|
||||
array_push( $this->supported_post_types, 'variable' );
|
||||
array_push( $this->supported_product_types, 'variable' );
|
||||
}
|
||||
|
||||
if ( Features::is_enabled( 'product-external-affiliate' ) ) {
|
||||
array_push( $this->supported_post_types, 'external' );
|
||||
array_push( $this->supported_product_types, 'external' );
|
||||
}
|
||||
|
||||
if ( Features::is_enabled( 'product-grouped' ) ) {
|
||||
array_push( $this->supported_post_types, 'grouped' );
|
||||
array_push( $this->supported_product_types, 'grouped' );
|
||||
}
|
||||
|
||||
$this->redirection_controller = new RedirectionController( $this->supported_post_types );
|
||||
$this->redirection_controller = new RedirectionController( $this->supported_product_types );
|
||||
|
||||
if ( \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ) {
|
||||
if ( ! Features::is_enabled( 'new-product-management-experience' ) ) {
|
||||
|
@ -210,22 +211,38 @@ class Init {
|
|||
* Get the product editor settings.
|
||||
*/
|
||||
private function get_product_editor_settings() {
|
||||
$layout_template_registry = wc_get_container()->get( BlockTemplateRegistry::class );
|
||||
$layout_template_logger = BlockTemplateLogger::get_instance();
|
||||
|
||||
$editor_settings = array();
|
||||
|
||||
$template_registry = wc_get_container()->get( BlockTemplateRegistry::class );
|
||||
$block_template_logger = BlockTemplateLogger::get_instance();
|
||||
foreach ( $layout_template_registry->get_all_registered() as $layout_template ) {
|
||||
$editor_settings['layoutTemplates'][] = $layout_template->to_json();
|
||||
|
||||
$block_template_logger->log_template_events_to_file( 'simple-product' );
|
||||
$block_template_logger->log_template_events_to_file( 'product-variation' );
|
||||
$layout_template_logger->log_template_events_to_file( $layout_template->get_id() );
|
||||
$editor_settings['layoutTemplateEvents'][] = $layout_template_logger->get_formatted_template_events( $layout_template->get_id() );
|
||||
}
|
||||
|
||||
$editor_settings['templates'] = array(
|
||||
'product' => $template_registry->get_registered( 'simple-product' )->get_formatted_template(),
|
||||
'product_variation' => $template_registry->get_registered( 'product-variation' )->get_formatted_template(),
|
||||
/**
|
||||
* Allows for new product template registration.
|
||||
*
|
||||
* @since 8.5.0
|
||||
*/
|
||||
$product_templates = apply_filters( 'woocommerce_product_editor_product_templates', $this->get_default_product_templates() );
|
||||
$product_templates = $this->create_default_product_template_by_custom_product_type( $product_templates );
|
||||
|
||||
usort(
|
||||
$product_templates,
|
||||
function ( $a, $b ) {
|
||||
return $a->get_order() - $b->get_order();
|
||||
}
|
||||
);
|
||||
|
||||
$editor_settings['templateEvents'] = array(
|
||||
'product' => $block_template_logger->get_formatted_template_events( 'simple-product' ),
|
||||
'product_variation' => $block_template_logger->get_formatted_template_events( 'product-variation' ),
|
||||
$editor_settings['productTemplates'] = array_map(
|
||||
function ( $product_template ) {
|
||||
return $product_template->to_json();
|
||||
},
|
||||
$product_templates
|
||||
);
|
||||
|
||||
$block_editor_context = new WP_Block_Editor_Context( array( 'name' => self::EDITOR_CONTEXT_NAME ) );
|
||||
|
@ -233,12 +250,128 @@ class Init {
|
|||
return get_block_editor_settings( $editor_settings, $block_editor_context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default product templates.
|
||||
*
|
||||
* @return array The default templates.
|
||||
*/
|
||||
private function get_default_product_templates() {
|
||||
$templates = array();
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => 'standard-product-template',
|
||||
'title' => __( 'Standard product', 'woocommerce' ),
|
||||
'description' => __( 'A single physical or virtual product, e.g. a t-shirt or an eBook.', 'woocommerce' ),
|
||||
'order' => 10,
|
||||
'icon' => 'shipping',
|
||||
'layout_template_id' => 'simple-product',
|
||||
'product_data' => array(
|
||||
'type' => 'simple',
|
||||
),
|
||||
)
|
||||
);
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => 'grouped-product-template',
|
||||
'title' => __( 'Grouped product', 'woocommerce' ),
|
||||
'description' => __( 'A set of products that go well together, e.g. camera kit.', 'woocommerce' ),
|
||||
'order' => 20,
|
||||
'icon' => 'group',
|
||||
'layout_template_id' => 'simple-product',
|
||||
'product_data' => array(
|
||||
'type' => 'grouped',
|
||||
),
|
||||
)
|
||||
);
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => 'affiliate-product-template',
|
||||
'title' => __( 'Affiliate product', 'woocommerce' ),
|
||||
'description' => __( 'A link to a product sold on a different website, e.g. brand collab.', 'woocommerce' ),
|
||||
'order' => 30,
|
||||
'icon' => 'link',
|
||||
'layout_template_id' => 'simple-product',
|
||||
'product_data' => array(
|
||||
'type' => 'external',
|
||||
),
|
||||
)
|
||||
);
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => 'variable-product-template',
|
||||
'title' => __( 'Variable product', 'woocommerce' ),
|
||||
'description' => __( 'A product with variations like color or size.', 'woocommerce' ),
|
||||
'order' => 40,
|
||||
'icon' => null,
|
||||
'layout_template_id' => 'simple-product',
|
||||
'product_data' => array(
|
||||
'type' => 'variable',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default product template by custom product type if it does not have a
|
||||
* template associated yet.
|
||||
*
|
||||
* @param array $templates The registered product templates.
|
||||
* @return array The new templates.
|
||||
*/
|
||||
private function create_default_product_template_by_custom_product_type( array $templates ) {
|
||||
// Getting the product types registered via the classic editor.
|
||||
$registered_product_types = wc_get_product_types();
|
||||
|
||||
$custom_product_types = array_filter(
|
||||
$registered_product_types,
|
||||
function ( $product_type ) {
|
||||
return ! in_array( $product_type, $this->supported_product_types, true );
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
$templates_with_product_type = array_filter(
|
||||
$templates,
|
||||
function ( $template ) {
|
||||
$product_data = $template->get_product_data();
|
||||
return ! is_null( $product_data ) && array_key_exists( 'type', $product_data );
|
||||
}
|
||||
);
|
||||
|
||||
$custom_product_types_on_templates = array_map(
|
||||
function ( $template ) {
|
||||
$product_data = $template->get_product_data();
|
||||
return $product_data['type'];
|
||||
},
|
||||
$templates_with_product_type
|
||||
);
|
||||
|
||||
foreach ( $custom_product_types as $product_type => $title ) {
|
||||
if ( in_array( $product_type, $custom_product_types_on_templates, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => $product_type . '-product-template',
|
||||
'title' => $title,
|
||||
'product_data' => array(
|
||||
'type' => $product_type,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register product editor templates.
|
||||
*/
|
||||
private function register_product_editor_templates() {
|
||||
$template_registry = wc_get_container()->get( BlockTemplateRegistry::class );
|
||||
|
||||
$template_registry->register( new SimpleProductTemplate() );
|
||||
$template_registry->register( new ProductVariationTemplate() );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
<?php
|
||||
/**
|
||||
* WooCommerce Product Block Editor
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
|
||||
|
||||
/**
|
||||
* The Product Template that represents the relation between the Product and
|
||||
* the LayoutTemplate (ProductFormTemplateInterface)
|
||||
*
|
||||
* @see ProductFormTemplateInterface
|
||||
*/
|
||||
class ProductTemplate {
|
||||
/**
|
||||
* The template id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The template title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* The product data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $product_data;
|
||||
|
||||
/**
|
||||
* The template order.
|
||||
*
|
||||
* @var Integer
|
||||
*/
|
||||
private $order = 999;
|
||||
|
||||
/**
|
||||
* The layout template id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $layout_template_id = null;
|
||||
|
||||
/**
|
||||
* The template description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $description = null;
|
||||
|
||||
/**
|
||||
* The template icon.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $icon = null;
|
||||
|
||||
/**
|
||||
* ProductTemplate constructor
|
||||
*
|
||||
* @param array $data The data.
|
||||
*/
|
||||
public function __construct( array $data ) {
|
||||
$this->id = $data['id'];
|
||||
$this->title = $data['title'];
|
||||
$this->product_data = $data['product_data'];
|
||||
|
||||
if ( isset( $data['order'] ) ) {
|
||||
$this->order = $data['order'];
|
||||
}
|
||||
|
||||
if ( isset( $data['layout_template_id'] ) ) {
|
||||
$this->layout_template_id = $data['layout_template_id'];
|
||||
}
|
||||
|
||||
if ( isset( $data['description'] ) ) {
|
||||
$this->description = $data['description'];
|
||||
}
|
||||
|
||||
if ( isset( $data['icon'] ) ) {
|
||||
$this->icon = $data['icon'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template ID.
|
||||
*
|
||||
* @return string The ID.
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template title.
|
||||
*
|
||||
* @return string The title.
|
||||
*/
|
||||
public function get_title() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the layout template ID.
|
||||
*
|
||||
* @return string The layout template ID.
|
||||
*/
|
||||
public function get_layout_template_id() {
|
||||
return $this->layout_template_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the layout template ID.
|
||||
*
|
||||
* @param string $layout_template_id The layout template ID.
|
||||
*/
|
||||
public function set_layout_template_id( string $layout_template_id ) {
|
||||
$this->layout_template_id = $layout_template_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product data.
|
||||
*
|
||||
* @return array The product data.
|
||||
*/
|
||||
public function get_product_data() {
|
||||
return $this->product_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template description.
|
||||
*
|
||||
* @return string The description.
|
||||
*/
|
||||
public function get_description() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the template description.
|
||||
*
|
||||
* @param string $description The template description.
|
||||
*/
|
||||
public function set_description( string $description ) {
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template icon.
|
||||
*
|
||||
* @return string The icon.
|
||||
*/
|
||||
public function get_icon() {
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the template icon.
|
||||
*
|
||||
* @see https://github.com/WordPress/gutenberg/tree/trunk/packages/icons.
|
||||
*
|
||||
* @param string $icon The icon name from the @wordpress/components or a url for an external image resource.
|
||||
*/
|
||||
public function set_icon( string $icon ) {
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template order.
|
||||
*
|
||||
* @return int The order.
|
||||
*/
|
||||
public function get_order() {
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the template order.
|
||||
*
|
||||
* @param int $order The template order.
|
||||
*/
|
||||
public function set_order( int $order ) {
|
||||
$this->order = $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product template as JSON like.
|
||||
*
|
||||
* @return array The JSON.
|
||||
*/
|
||||
public function to_json() {
|
||||
return array(
|
||||
'id' => $this->get_id(),
|
||||
'title' => $this->get_title(),
|
||||
'description' => $this->get_description(),
|
||||
'icon' => $this->get_icon(),
|
||||
'order' => $this->get_order(),
|
||||
'layoutTemplateId' => $this->get_layout_template_id(),
|
||||
'productData' => $this->get_product_data(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -14,19 +14,19 @@ use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
|||
class RedirectionController {
|
||||
|
||||
/**
|
||||
* Supported post types.
|
||||
* Supported product types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $supported_post_types;
|
||||
private $supported_product_types;
|
||||
|
||||
/**
|
||||
* Set up the hooks used for redirection.
|
||||
*
|
||||
* @param array $supported_post_types Array of supported post types.
|
||||
* @param array $supported_product_types Array of supported product types.
|
||||
*/
|
||||
public function __construct( $supported_post_types ) {
|
||||
$this->supported_post_types = $supported_post_types;
|
||||
public function __construct( $supported_product_types ) {
|
||||
$this->supported_product_types = $supported_product_types;
|
||||
|
||||
if ( \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ) {
|
||||
add_action( 'current_screen', array( $this, 'maybe_redirect_to_new_editor' ), 30, 0 );
|
||||
|
@ -65,7 +65,7 @@ class RedirectionController {
|
|||
$product = $product_id ? wc_get_product( $product_id ) : null;
|
||||
$digital_product = $product->is_downloadable() || $product->is_virtual();
|
||||
|
||||
if ( $product && in_array( $product->get_type(), $this->supported_post_types, true ) ) {
|
||||
if ( $product && in_array( $product->get_type(), $this->supported_product_types, true ) ) {
|
||||
if ( Features::is_enabled( 'product-virtual-downloadable' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ final class BlockTemplateRegistry {
|
|||
*
|
||||
* @param string $id ID of the template.
|
||||
*/
|
||||
public function get_registered( $id ): BlockTemplateInterface {
|
||||
public function get_registered( $id ): ?BlockTemplateInterface {
|
||||
return isset( $this->templates[ $id ] ) ? $this->templates[ $id ] : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,4 +131,19 @@ abstract class AbstractBlockTemplate implements BlockTemplateInterface {
|
|||
|
||||
return $inner_blocks_formatted_template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template as JSON like array.
|
||||
*
|
||||
* @return array The JSON.
|
||||
*/
|
||||
public function to_json(): array {
|
||||
return array(
|
||||
'id' => $this->get_id(),
|
||||
'title' => $this->get_title(),
|
||||
'description' => $this->get_description(),
|
||||
'area' => $this->get_area(),
|
||||
'blockTemplates' => $this->get_formatted_template(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use Automattic\WooCommerce\Admin\Features\Features;
|
|||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\ProductFormTemplateInterface;
|
||||
|
||||
/**
|
||||
* Simple Product Template.
|
||||
* Product Variation Template.
|
||||
*/
|
||||
class ProductVariationTemplate extends AbstractProductFormTemplate implements ProductFormTemplateInterface {
|
||||
/**
|
||||
|
@ -28,7 +28,7 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
|
|||
const SINGLE_VARIATION_NOTICE_DISMISSED_OPTION = 'woocommerce_single_variation_notice_dismissed';
|
||||
|
||||
/**
|
||||
* SimpleProductTemplate constructor.
|
||||
* ProductVariationTemplate constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->add_group_blocks();
|
||||
|
|
|
@ -198,6 +198,13 @@ class SimpleProductTemplate extends AbstractProductFormTemplate implements Produ
|
|||
),
|
||||
)
|
||||
);
|
||||
$basic_details->add_block(
|
||||
array(
|
||||
'id' => 'product-details-section-description',
|
||||
'blockName' => 'woocommerce/product-details-section-description',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$basic_details->add_block(
|
||||
array(
|
||||
'id' => 'product-name',
|
||||
|
|
Loading…
Reference in New Issue