2022-04-12 15:16:00 +00:00
|
|
|
/* eslint-disable @wordpress/no-unsafe-wp-apis */
|
|
|
|
|
2019-01-10 19:01:49 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2022-04-12 15:16:00 +00:00
|
|
|
import { useCallback, useState } from 'react';
|
2019-01-10 19:01:49 +00:00
|
|
|
import { __ } from '@wordpress/i18n';
|
2019-01-13 17:22:15 +00:00
|
|
|
import {
|
|
|
|
AlignmentToolbar,
|
|
|
|
BlockControls,
|
2019-02-08 23:57:58 +00:00
|
|
|
InnerBlocks,
|
2019-01-13 17:22:15 +00:00
|
|
|
InspectorControls,
|
2020-12-21 16:09:10 +00:00
|
|
|
MediaReplaceFlow,
|
2019-10-24 13:55:44 +00:00
|
|
|
RichText,
|
2022-04-12 15:16:00 +00:00
|
|
|
__experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles,
|
|
|
|
__experimentalPanelColorGradientSettings as PanelColorGradientSettings,
|
|
|
|
__experimentalUseGradient as useGradient,
|
2020-05-12 20:31:49 +00:00
|
|
|
} from '@wordpress/block-editor';
|
2020-05-12 09:08:53 +00:00
|
|
|
import { withSelect } from '@wordpress/data';
|
2019-01-10 19:01:49 +00:00
|
|
|
import {
|
|
|
|
Button,
|
2019-05-03 14:37:52 +00:00
|
|
|
FocalPointPicker,
|
2019-01-10 19:01:49 +00:00
|
|
|
PanelBody,
|
|
|
|
Placeholder,
|
2019-01-13 17:22:15 +00:00
|
|
|
RangeControl,
|
2019-02-05 20:49:24 +00:00
|
|
|
ResizableBox,
|
2019-01-10 19:01:49 +00:00
|
|
|
Spinner,
|
2019-01-13 17:22:15 +00:00
|
|
|
ToggleControl,
|
2020-12-21 16:03:54 +00:00
|
|
|
ToolbarGroup,
|
2019-01-10 19:01:49 +00:00
|
|
|
withSpokenMessages,
|
2022-04-12 15:16:00 +00:00
|
|
|
__experimentalToggleGroupControl as ToggleGroupControl,
|
|
|
|
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
2019-01-10 19:01:49 +00:00
|
|
|
} from '@wordpress/components';
|
2019-01-13 17:22:15 +00:00
|
|
|
import classnames from 'classnames';
|
2020-12-14 11:54:34 +00:00
|
|
|
import { Component } from '@wordpress/element';
|
2020-05-12 09:08:53 +00:00
|
|
|
import { compose, createHigherOrderComponent } from '@wordpress/compose';
|
2019-08-02 11:56:53 +00:00
|
|
|
import { isEmpty } from 'lodash';
|
2019-01-10 19:01:49 +00:00
|
|
|
import PropTypes from 'prop-types';
|
2020-09-02 08:21:46 +00:00
|
|
|
import ProductControl from '@woocommerce/editor-components/product-control';
|
|
|
|
import ErrorPlaceholder from '@woocommerce/editor-components/error-placeholder';
|
2022-02-23 07:16:47 +00:00
|
|
|
import TextToolbarButton from '@woocommerce/editor-components/text-toolbar-button';
|
2019-11-08 16:30:11 +00:00
|
|
|
import { withProduct } from '@woocommerce/block-hocs';
|
2022-02-01 16:54:38 +00:00
|
|
|
import { Icon, starEmpty } from '@wordpress/icons';
|
2019-01-10 19:01:49 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
2022-04-12 15:16:00 +00:00
|
|
|
import { calculateBackgroundImagePosition, dimRatioToClass } from './utils';
|
2019-01-14 22:22:00 +00:00
|
|
|
import {
|
|
|
|
getImageSrcFromProduct,
|
|
|
|
getImageIdFromProduct,
|
|
|
|
} from '../../utils/products';
|
2022-04-12 15:16:00 +00:00
|
|
|
import { useThrottle } from '../../utils/useThrottle';
|
|
|
|
|
|
|
|
const ConstrainedResizable = ( { className = '', onResize, ...props } ) => {
|
|
|
|
const [ isResizing, setIsResizing ] = useState( false );
|
|
|
|
|
|
|
|
const classNames = classnames( className, {
|
|
|
|
'is-resizing': isResizing,
|
|
|
|
} );
|
|
|
|
const throttledResize = useThrottle(
|
|
|
|
( event, direction, elt ) => {
|
|
|
|
if ( ! isResizing ) setIsResizing( true );
|
|
|
|
onResize( event, direction, elt );
|
|
|
|
},
|
|
|
|
50,
|
|
|
|
{ leading: true }
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ResizableBox
|
|
|
|
className={ classNames }
|
|
|
|
enable={ { bottom: true } }
|
|
|
|
onResize={ throttledResize }
|
|
|
|
onResizeStop={ ( ...args ) => {
|
|
|
|
onResize( ...args );
|
|
|
|
setIsResizing( false );
|
|
|
|
} }
|
|
|
|
{ ...props }
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
2019-02-05 20:49:24 +00:00
|
|
|
|
2019-01-10 19:01:49 +00:00
|
|
|
/**
|
|
|
|
* Component to handle edit mode of "Featured Product".
|
2020-09-20 23:54:08 +00:00
|
|
|
*
|
2022-04-08 13:47:19 +00:00
|
|
|
* @param {Object} props Incoming props for the component.
|
|
|
|
* @param {Object} props.attributes Incoming block attributes.
|
|
|
|
* @param {function(any):any} props.debouncedSpeak Function for delayed speak.
|
|
|
|
* @param {string} props.error Error message.
|
|
|
|
* @param {function(any):any} props.getProduct Function for getting the product.
|
|
|
|
* @param {boolean} props.isLoading Whether product is loading or not.
|
|
|
|
* @param {boolean} props.isSelected Whether block is selected or not.
|
|
|
|
* @param {Object} props.product Product object.
|
|
|
|
* @param {function(any):any} props.setAttributes Setter for attributes.
|
|
|
|
* @param {function():any} props.triggerUrlUpdate Function for triggering a url update for product.
|
2019-01-10 19:01:49 +00:00
|
|
|
*/
|
2019-09-05 15:09:31 +00:00
|
|
|
const FeaturedProduct = ( {
|
|
|
|
attributes,
|
|
|
|
debouncedSpeak,
|
|
|
|
error,
|
|
|
|
getProduct,
|
|
|
|
isLoading,
|
|
|
|
isSelected,
|
|
|
|
product,
|
|
|
|
setAttributes,
|
2020-05-12 09:08:53 +00:00
|
|
|
triggerUrlUpdate = () => void null,
|
2019-09-05 15:09:31 +00:00
|
|
|
} ) => {
|
2022-04-12 15:16:00 +00:00
|
|
|
const { gradientValue, setGradient } = useGradient();
|
|
|
|
const onResize = useCallback(
|
|
|
|
( _event, _direction, elt ) => {
|
|
|
|
setAttributes( { minHeight: parseInt( elt.style.height, 10 ) } );
|
|
|
|
},
|
|
|
|
[ setAttributes ]
|
|
|
|
);
|
|
|
|
|
2019-08-02 11:56:53 +00:00
|
|
|
const renderApiError = () => (
|
2019-09-09 12:04:54 +00:00
|
|
|
<ErrorPlaceholder
|
2019-08-02 11:56:53 +00:00
|
|
|
className="wc-block-featured-product-error"
|
|
|
|
error={ error }
|
|
|
|
isLoading={ isLoading }
|
|
|
|
onRetry={ getProduct }
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
|
|
|
|
const renderEditMode = () => {
|
2019-01-10 19:01:49 +00:00
|
|
|
const onDone = () => {
|
|
|
|
setAttributes( { editMode: false } );
|
|
|
|
debouncedSpeak(
|
|
|
|
__(
|
|
|
|
'Showing Featured Product block preview.',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2020-12-14 11:54:34 +00:00
|
|
|
<>
|
2019-08-02 11:56:53 +00:00
|
|
|
{ getBlockControls() }
|
2019-07-11 10:12:44 +00:00
|
|
|
<Placeholder
|
2022-02-01 16:54:38 +00:00
|
|
|
icon={ <Icon icon={ starEmpty } /> }
|
2019-09-09 10:52:48 +00:00
|
|
|
label={ __(
|
|
|
|
'Featured Product',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
2019-07-11 10:12:44 +00:00
|
|
|
className="wc-block-featured-product"
|
|
|
|
>
|
|
|
|
{ __(
|
|
|
|
'Visually highlight a product or variation and encourage prompt action',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
<div className="wc-block-featured-product__selection">
|
|
|
|
<ProductControl
|
|
|
|
selected={ attributes.productId || 0 }
|
2019-09-25 14:22:36 +00:00
|
|
|
showVariations
|
2019-07-11 10:12:44 +00:00
|
|
|
onChange={ ( value = [] ) => {
|
|
|
|
const id = value[ 0 ] ? value[ 0 ].id : 0;
|
2019-09-05 15:09:31 +00:00
|
|
|
setAttributes( {
|
|
|
|
productId: id,
|
|
|
|
mediaId: 0,
|
|
|
|
mediaSrc: '',
|
|
|
|
} );
|
2020-05-12 09:08:53 +00:00
|
|
|
triggerUrlUpdate();
|
2019-07-11 10:12:44 +00:00
|
|
|
} }
|
|
|
|
/>
|
2020-05-28 10:20:47 +00:00
|
|
|
<Button isPrimary onClick={ onDone }>
|
2019-07-11 10:12:44 +00:00
|
|
|
{ __( 'Done', 'woo-gutenberg-products-block' ) }
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</Placeholder>
|
2020-12-14 11:54:34 +00:00
|
|
|
</>
|
2019-01-10 19:01:49 +00:00
|
|
|
);
|
2019-08-02 11:56:53 +00:00
|
|
|
};
|
2019-07-11 10:12:44 +00:00
|
|
|
|
2019-08-02 11:56:53 +00:00
|
|
|
const getBlockControls = () => {
|
2020-12-21 16:09:10 +00:00
|
|
|
const { contentAlign, editMode, mediaSrc } = attributes;
|
2019-07-11 10:12:44 +00:00
|
|
|
const mediaId = attributes.mediaId || getImageIdFromProduct( product );
|
|
|
|
|
|
|
|
return (
|
|
|
|
<BlockControls>
|
|
|
|
<AlignmentToolbar
|
|
|
|
value={ contentAlign }
|
|
|
|
onChange={ ( nextAlign ) => {
|
|
|
|
setAttributes( { contentAlign: nextAlign } );
|
|
|
|
} }
|
|
|
|
/>
|
2022-02-23 07:16:47 +00:00
|
|
|
<ToolbarGroup>
|
|
|
|
<MediaReplaceFlow
|
|
|
|
mediaId={ mediaId }
|
|
|
|
mediaURL={ mediaSrc }
|
|
|
|
accept="image/*"
|
|
|
|
onSelect={ ( media ) => {
|
|
|
|
setAttributes( {
|
|
|
|
mediaId: media.id,
|
|
|
|
mediaSrc: media.url,
|
|
|
|
} );
|
|
|
|
} }
|
|
|
|
allowedTypes={ [ 'image' ] }
|
|
|
|
/>
|
|
|
|
{ mediaId && mediaSrc ? (
|
|
|
|
<TextToolbarButton
|
|
|
|
onClick={ () =>
|
|
|
|
setAttributes( { mediaId: 0, mediaSrc: '' } )
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{ __( 'Reset', 'woo-gutenberg-products-block' ) }
|
|
|
|
</TextToolbarButton>
|
|
|
|
) : null }
|
|
|
|
</ToolbarGroup>
|
2020-12-21 16:03:54 +00:00
|
|
|
<ToolbarGroup
|
2019-07-11 10:12:44 +00:00
|
|
|
controls={ [
|
|
|
|
{
|
|
|
|
icon: 'edit',
|
2021-11-10 13:53:07 +00:00
|
|
|
title: __(
|
|
|
|
'Edit selected product',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
),
|
2019-09-09 10:52:48 +00:00
|
|
|
onClick: () =>
|
|
|
|
setAttributes( { editMode: ! editMode } ),
|
2019-07-11 10:12:44 +00:00
|
|
|
isActive: editMode,
|
|
|
|
},
|
|
|
|
] }
|
|
|
|
/>
|
|
|
|
</BlockControls>
|
|
|
|
);
|
2019-08-02 11:56:53 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const getInspectorControls = () => {
|
|
|
|
const url = attributes.mediaSrc || getImageSrcFromProduct( product );
|
|
|
|
const { focalPoint = { x: 0.5, y: 0.5 } } = attributes;
|
2019-08-08 14:43:10 +00:00
|
|
|
// FocalPointPicker was introduced in Gutenberg 5.0 (WordPress 5.2),
|
|
|
|
// so we need to check if it exists before using it.
|
|
|
|
const focalPointPickerExists = typeof FocalPointPicker === 'function';
|
2019-08-02 11:56:53 +00:00
|
|
|
|
|
|
|
return (
|
2022-04-12 15:16:00 +00:00
|
|
|
<>
|
|
|
|
<InspectorControls key="inspector">
|
|
|
|
<PanelBody
|
|
|
|
title={ __(
|
|
|
|
'Content',
|
2019-09-09 10:52:48 +00:00
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
2022-04-12 15:16:00 +00:00
|
|
|
>
|
|
|
|
<ToggleControl
|
|
|
|
label={ __(
|
|
|
|
'Show description',
|
2019-09-09 10:52:48 +00:00
|
|
|
'woo-gutenberg-products-block'
|
2022-02-01 14:01:31 +00:00
|
|
|
) }
|
2022-04-12 15:16:00 +00:00
|
|
|
checked={ attributes.showDesc }
|
|
|
|
onChange={ () =>
|
|
|
|
setAttributes( {
|
|
|
|
showDesc: ! attributes.showDesc,
|
|
|
|
} )
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
<ToggleControl
|
|
|
|
label={ __(
|
|
|
|
'Show price',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
checked={ attributes.showPrice }
|
|
|
|
onChange={ () =>
|
|
|
|
setAttributes( {
|
|
|
|
showPrice: ! attributes.showPrice,
|
|
|
|
} )
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</PanelBody>
|
|
|
|
{ !! url && (
|
|
|
|
<>
|
|
|
|
{ focalPointPickerExists && (
|
|
|
|
<PanelBody
|
|
|
|
title={ __(
|
|
|
|
'Media settings',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
>
|
|
|
|
<ToggleGroupControl
|
|
|
|
help={
|
|
|
|
<>
|
|
|
|
<p>
|
|
|
|
{ __(
|
|
|
|
'Choose “Cover” if you want the image to scale automatically to always fit its container.',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
{ __(
|
|
|
|
'Note: by choosing “Cover” you will lose the ability to freely move the focal point precisely.',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
</p>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
label={ __(
|
|
|
|
'Image fit',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
value={ attributes.imageFit }
|
|
|
|
onChange={ ( value ) =>
|
|
|
|
setAttributes( {
|
|
|
|
imageFit: value,
|
|
|
|
} )
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<ToggleGroupControlOption
|
|
|
|
label={ __(
|
|
|
|
'None',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
value="none"
|
|
|
|
/>
|
|
|
|
<ToggleGroupControlOption
|
|
|
|
/* translators: "Cover" is a verb that indicates an image covering the entire container. */
|
|
|
|
label={ __(
|
|
|
|
'Cover',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
value="cover"
|
|
|
|
/>
|
|
|
|
</ToggleGroupControl>
|
|
|
|
<FocalPointPicker
|
|
|
|
label={ __(
|
|
|
|
'Focal Point Picker',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
|
|
|
url={ url }
|
|
|
|
value={ focalPoint }
|
|
|
|
onChange={ ( value ) =>
|
|
|
|
setAttributes( {
|
|
|
|
focalPoint: value,
|
|
|
|
} )
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</PanelBody>
|
|
|
|
) }
|
|
|
|
<PanelColorGradientSettings
|
|
|
|
__experimentalHasMultipleOrigins
|
|
|
|
__experimentalIsRenderedInSidebar
|
|
|
|
title={ __(
|
|
|
|
'Overlay',
|
2019-09-05 15:09:31 +00:00
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
2022-04-12 15:16:00 +00:00
|
|
|
initialOpen={ true }
|
|
|
|
settings={ [
|
|
|
|
{
|
|
|
|
gradientValue,
|
|
|
|
colorValue: attributes.overlayColor,
|
|
|
|
onColorChange: ( overlayColor ) =>
|
|
|
|
setAttributes( { overlayColor } ),
|
|
|
|
onGradientChange: (
|
|
|
|
overlayGradient
|
|
|
|
) => {
|
|
|
|
setGradient( overlayGradient );
|
|
|
|
setAttributes( {
|
|
|
|
overlayGradient,
|
|
|
|
} );
|
|
|
|
},
|
|
|
|
label: __(
|
|
|
|
'Color',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
),
|
|
|
|
},
|
|
|
|
] }
|
|
|
|
>
|
|
|
|
<RangeControl
|
2021-11-08 14:07:42 +00:00
|
|
|
label={ __(
|
2022-04-12 15:16:00 +00:00
|
|
|
'Opacity',
|
2021-11-08 14:07:42 +00:00
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
) }
|
2022-04-12 15:16:00 +00:00
|
|
|
value={ attributes.dimRatio }
|
|
|
|
onChange={ ( dimRatio ) =>
|
|
|
|
setAttributes( { dimRatio } )
|
2019-09-05 15:09:31 +00:00
|
|
|
}
|
2022-04-12 15:16:00 +00:00
|
|
|
min={ 0 }
|
|
|
|
max={ 100 }
|
|
|
|
step={ 10 }
|
|
|
|
required
|
2019-08-08 14:43:10 +00:00
|
|
|
/>
|
2022-04-12 15:16:00 +00:00
|
|
|
</PanelColorGradientSettings>
|
|
|
|
</>
|
|
|
|
) }
|
|
|
|
</InspectorControls>
|
|
|
|
<InspectorControls __experimentalGroup="dimensions"></InspectorControls>
|
|
|
|
</>
|
2019-08-02 11:56:53 +00:00
|
|
|
);
|
|
|
|
};
|
2019-07-11 10:12:44 +00:00
|
|
|
|
2019-08-02 11:56:53 +00:00
|
|
|
const renderProduct = () => {
|
2019-01-13 17:22:15 +00:00
|
|
|
const {
|
|
|
|
contentAlign,
|
|
|
|
dimRatio,
|
2019-05-03 14:37:52 +00:00
|
|
|
focalPoint,
|
2022-04-12 15:16:00 +00:00
|
|
|
imageFit,
|
|
|
|
mediaSrc,
|
|
|
|
minHeight,
|
|
|
|
overlayColor,
|
|
|
|
overlayGradient,
|
2019-01-13 17:22:15 +00:00
|
|
|
showDesc,
|
|
|
|
showPrice,
|
|
|
|
} = attributes;
|
2022-04-12 15:16:00 +00:00
|
|
|
|
2019-01-13 17:22:15 +00:00
|
|
|
const classes = classnames(
|
|
|
|
'wc-block-featured-product',
|
2022-04-12 15:16:00 +00:00
|
|
|
dimRatioToClass( dimRatio ),
|
2019-01-13 17:22:15 +00:00
|
|
|
{
|
2019-10-24 13:55:44 +00:00
|
|
|
'is-selected': isSelected && attributes.productId !== 'preview',
|
2019-08-02 11:56:53 +00:00
|
|
|
'is-loading': ! product && isLoading,
|
|
|
|
'is-not-found': ! product && ! isLoading,
|
2019-01-13 17:22:15 +00:00
|
|
|
'has-background-dim': dimRatio !== 0,
|
|
|
|
},
|
2022-02-01 14:01:31 +00:00
|
|
|
contentAlign !== 'center' && `has-${ contentAlign }-content`
|
2019-01-13 17:22:15 +00:00
|
|
|
);
|
|
|
|
|
2022-04-12 15:16:00 +00:00
|
|
|
const wrapperStyle = {
|
|
|
|
...getSpacingClassesAndStyles( attributes ).style,
|
|
|
|
minHeight,
|
|
|
|
};
|
2019-07-11 10:12:44 +00:00
|
|
|
|
2022-04-12 15:16:00 +00:00
|
|
|
const backgroundImageSrc =
|
|
|
|
mediaSrc || getImageSrcFromProduct( product );
|
2019-01-10 19:01:49 +00:00
|
|
|
|
2022-04-12 15:16:00 +00:00
|
|
|
const backgroundImageStyle = {
|
|
|
|
...calculateBackgroundImagePosition( focalPoint ),
|
|
|
|
objectFit: imageFit,
|
|
|
|
};
|
|
|
|
|
|
|
|
const overlayStyle = {
|
|
|
|
background: overlayGradient,
|
|
|
|
backgroundColor: overlayColor,
|
2019-02-05 20:49:24 +00:00
|
|
|
};
|
|
|
|
|
2019-01-10 19:01:49 +00:00
|
|
|
return (
|
2022-04-12 15:16:00 +00:00
|
|
|
<>
|
|
|
|
<ConstrainedResizable
|
|
|
|
enable={ { bottom: true } }
|
|
|
|
onResize={ onResize }
|
|
|
|
showHandle={ isSelected }
|
|
|
|
style={ { minHeight } }
|
|
|
|
/>
|
|
|
|
<div className={ classes }>
|
|
|
|
<div
|
|
|
|
className="wc-block-featured-product__wrapper"
|
|
|
|
style={ wrapperStyle }
|
|
|
|
>
|
2019-07-11 10:12:44 +00:00
|
|
|
<div
|
2022-04-12 15:16:00 +00:00
|
|
|
className="wc-block-featured-product__overlay"
|
|
|
|
style={ overlayStyle }
|
2019-07-11 10:12:44 +00:00
|
|
|
/>
|
2022-04-12 15:16:00 +00:00
|
|
|
<img
|
|
|
|
alt={ product.short_description }
|
|
|
|
className="wc-block-featured-product__background-image"
|
|
|
|
src={ backgroundImageSrc }
|
|
|
|
style={ backgroundImageStyle }
|
|
|
|
/>
|
|
|
|
<h2
|
|
|
|
className="wc-block-featured-product__title"
|
2019-09-05 15:09:31 +00:00
|
|
|
dangerouslySetInnerHTML={ {
|
2022-04-12 15:16:00 +00:00
|
|
|
__html: product.name,
|
2019-09-05 15:09:31 +00:00
|
|
|
} }
|
2019-07-11 10:12:44 +00:00
|
|
|
/>
|
2022-04-12 15:16:00 +00:00
|
|
|
{ ! isEmpty( product.variation ) && (
|
|
|
|
<h3
|
|
|
|
className="wc-block-featured-product__variation"
|
|
|
|
dangerouslySetInnerHTML={ {
|
|
|
|
__html: product.variation,
|
|
|
|
} }
|
|
|
|
/>
|
|
|
|
) }
|
|
|
|
{ showDesc && (
|
|
|
|
<div
|
|
|
|
className="wc-block-featured-product__description"
|
|
|
|
dangerouslySetInnerHTML={ {
|
|
|
|
__html: product.short_description,
|
|
|
|
} }
|
|
|
|
/>
|
|
|
|
) }
|
|
|
|
{ showPrice && (
|
|
|
|
<div
|
|
|
|
className="wc-block-featured-product__price"
|
|
|
|
dangerouslySetInnerHTML={ {
|
|
|
|
__html: product.price_html,
|
|
|
|
} }
|
|
|
|
/>
|
|
|
|
) }
|
|
|
|
<div className="wc-block-featured-product__link">
|
|
|
|
{ renderButton() }
|
|
|
|
</div>
|
2019-07-11 10:12:44 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-04-12 15:16:00 +00:00
|
|
|
</>
|
2019-07-11 10:12:44 +00:00
|
|
|
);
|
2019-08-02 11:56:53 +00:00
|
|
|
};
|
|
|
|
|
2019-10-24 13:55:44 +00:00
|
|
|
const renderButton = () => {
|
|
|
|
const buttonClasses = classnames(
|
|
|
|
'wp-block-button__link',
|
|
|
|
'is-style-fill'
|
|
|
|
);
|
|
|
|
const buttonStyle = {
|
|
|
|
backgroundColor: 'vivid-green-cyan',
|
|
|
|
borderRadius: '5px',
|
|
|
|
};
|
|
|
|
const wrapperStyle = {
|
|
|
|
width: '100%',
|
|
|
|
};
|
|
|
|
return attributes.productId === 'preview' ? (
|
|
|
|
<div className="wp-block-button aligncenter" style={ wrapperStyle }>
|
|
|
|
<RichText.Content
|
|
|
|
tagName="a"
|
|
|
|
className={ buttonClasses }
|
2020-04-24 13:36:47 +00:00
|
|
|
href={ product.permalink }
|
2019-10-24 13:55:44 +00:00
|
|
|
title={ attributes.linkText }
|
|
|
|
style={ buttonStyle }
|
|
|
|
value={ attributes.linkText }
|
2020-04-24 13:36:47 +00:00
|
|
|
target={ product.permalink }
|
2019-10-24 13:55:44 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<InnerBlocks
|
|
|
|
template={ [
|
|
|
|
[
|
2022-02-22 14:49:11 +00:00
|
|
|
'core/buttons',
|
2022-03-30 08:57:06 +00:00
|
|
|
{
|
|
|
|
layout: { type: 'flex', justifyContent: 'center' },
|
|
|
|
},
|
2022-02-22 14:49:11 +00:00
|
|
|
[
|
|
|
|
[
|
|
|
|
'core/button',
|
|
|
|
{
|
|
|
|
text: __(
|
|
|
|
'Shop now',
|
|
|
|
'woo-gutenberg-products-block'
|
|
|
|
),
|
|
|
|
url: product.permalink,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
],
|
2019-10-24 13:55:44 +00:00
|
|
|
],
|
|
|
|
] }
|
|
|
|
templateLock="all"
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2019-08-02 11:56:53 +00:00
|
|
|
const renderNoProduct = () => (
|
|
|
|
<Placeholder
|
|
|
|
className="wc-block-featured-product"
|
2022-02-01 16:54:38 +00:00
|
|
|
icon={ <Icon icon={ starEmpty } /> }
|
2019-08-02 11:56:53 +00:00
|
|
|
label={ __( 'Featured Product', 'woo-gutenberg-products-block' ) }
|
|
|
|
>
|
|
|
|
{ isLoading ? (
|
|
|
|
<Spinner />
|
|
|
|
) : (
|
|
|
|
__( 'No product is selected.', 'woo-gutenberg-products-block' )
|
|
|
|
) }
|
|
|
|
</Placeholder>
|
|
|
|
);
|
|
|
|
|
|
|
|
const { editMode } = attributes;
|
|
|
|
|
|
|
|
if ( error ) {
|
|
|
|
return renderApiError();
|
2019-07-11 10:12:44 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 11:56:53 +00:00
|
|
|
if ( editMode ) {
|
|
|
|
return renderEditMode();
|
2019-07-11 10:12:44 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 11:56:53 +00:00
|
|
|
return (
|
2020-12-14 11:54:34 +00:00
|
|
|
<>
|
2019-08-02 11:56:53 +00:00
|
|
|
{ getBlockControls() }
|
|
|
|
{ getInspectorControls() }
|
2019-09-05 15:09:31 +00:00
|
|
|
{ product ? renderProduct() : renderNoProduct() }
|
2020-12-14 11:54:34 +00:00
|
|
|
</>
|
2019-08-02 11:56:53 +00:00
|
|
|
);
|
|
|
|
};
|
2019-01-10 19:01:49 +00:00
|
|
|
|
|
|
|
FeaturedProduct.propTypes = {
|
|
|
|
/**
|
2019-02-05 20:49:24 +00:00
|
|
|
* The attributes for this block.
|
2019-01-10 19:01:49 +00:00
|
|
|
*/
|
|
|
|
attributes: PropTypes.object.isRequired,
|
2019-02-05 20:49:24 +00:00
|
|
|
/**
|
|
|
|
* Whether this block is currently active.
|
|
|
|
*/
|
|
|
|
isSelected: PropTypes.bool.isRequired,
|
2019-01-10 19:01:49 +00:00
|
|
|
/**
|
|
|
|
* The register block name.
|
|
|
|
*/
|
|
|
|
name: PropTypes.string.isRequired,
|
|
|
|
/**
|
2019-02-05 20:49:24 +00:00
|
|
|
* A callback to update attributes.
|
2019-01-10 19:01:49 +00:00
|
|
|
*/
|
|
|
|
setAttributes: PropTypes.func.isRequired,
|
2019-08-02 11:56:53 +00:00
|
|
|
// from withProduct
|
|
|
|
error: PropTypes.object,
|
|
|
|
getProduct: PropTypes.func,
|
|
|
|
isLoading: PropTypes.bool,
|
|
|
|
product: PropTypes.shape( {
|
|
|
|
name: PropTypes.node,
|
|
|
|
variation: PropTypes.node,
|
|
|
|
description: PropTypes.node,
|
|
|
|
price_html: PropTypes.node,
|
|
|
|
permalink: PropTypes.string,
|
|
|
|
} ),
|
2019-01-10 19:01:49 +00:00
|
|
|
// from withSpokenMessages
|
|
|
|
debouncedSpeak: PropTypes.func.isRequired,
|
2020-05-12 09:08:53 +00:00
|
|
|
triggerUrlUpdate: PropTypes.func,
|
2019-01-10 19:01:49 +00:00
|
|
|
};
|
|
|
|
|
2019-01-13 17:22:15 +00:00
|
|
|
export default compose( [
|
2019-08-02 11:56:53 +00:00
|
|
|
withProduct,
|
2019-01-13 17:22:15 +00:00
|
|
|
withSpokenMessages,
|
2020-05-12 09:08:53 +00:00
|
|
|
withSelect( ( select, { clientId }, { dispatch } ) => {
|
|
|
|
const Block = select( 'core/block-editor' ).getBlock( clientId );
|
|
|
|
const buttonBlockId = Block?.innerBlocks[ 0 ]?.clientId || '';
|
|
|
|
const currentButtonAttributes =
|
|
|
|
Block?.innerBlocks[ 0 ]?.attributes || {};
|
|
|
|
const updateBlockAttributes = ( attributes ) => {
|
|
|
|
if ( buttonBlockId ) {
|
|
|
|
dispatch( 'core/block-editor' ).updateBlockAttributes(
|
|
|
|
buttonBlockId,
|
|
|
|
attributes
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return { updateBlockAttributes, currentButtonAttributes };
|
|
|
|
} ),
|
|
|
|
createHigherOrderComponent( ( ProductComponent ) => {
|
|
|
|
class WrappedComponent extends Component {
|
|
|
|
state = {
|
|
|
|
doUrlUpdate: false,
|
|
|
|
};
|
|
|
|
componentDidUpdate() {
|
|
|
|
const {
|
|
|
|
attributes,
|
|
|
|
updateBlockAttributes,
|
|
|
|
currentButtonAttributes,
|
|
|
|
product,
|
|
|
|
} = this.props;
|
|
|
|
if (
|
|
|
|
this.state.doUrlUpdate &&
|
|
|
|
! attributes.editMode &&
|
|
|
|
product?.permalink &&
|
|
|
|
currentButtonAttributes?.url &&
|
|
|
|
product.permalink !== currentButtonAttributes.url
|
|
|
|
) {
|
|
|
|
updateBlockAttributes( {
|
|
|
|
...currentButtonAttributes,
|
|
|
|
url: product.permalink,
|
|
|
|
} );
|
|
|
|
this.setState( { doUrlUpdate: false } );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
triggerUrlUpdate = () => {
|
|
|
|
this.setState( { doUrlUpdate: true } );
|
|
|
|
};
|
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<ProductComponent
|
|
|
|
triggerUrlUpdate={ this.triggerUrlUpdate }
|
|
|
|
{ ...this.props }
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return WrappedComponent;
|
|
|
|
}, 'withUpdateButtonAttributes' ),
|
2019-01-13 17:22:15 +00:00
|
|
|
] )( FeaturedProduct );
|