Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Add Product Query Support for Atomic Rating Block #7352

Merged
merged 14 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ export const blockAttributes = {
type: 'number',
default: 0,
},
isDescendentOfQueryLoop: {
type: 'boolean',
default: false,
},
};

export default blockAttributes;
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import { __, _n, sprintf } from '@wordpress/i18n';
import classnames from 'classnames';
import {
Expand All @@ -14,20 +13,43 @@ import {
useTypographyProps,
} from '@woocommerce/base-hooks';
import { withProductDataContext } from '@woocommerce/shared-hocs';
import { isNumber, ProductResponseItem } from '@woocommerce/types';

/**
* Internal dependencies
*/
import './style.scss';

type Props = {
className?: string;
};

const getAverageRating = (
product: Omit< ProductResponseItem, 'average_rating' > & {
average_rating: string;
}
) => {
const rating = parseFloat( product.average_rating );

return Number.isFinite( rating ) && rating > 0 ? rating : 0;
};

const getRatingCount = ( product: ProductResponseItem ) => {
const count = isNumber( product.review_count )
? product.review_count
: parseInt( product.review_count, 10 );

return Number.isFinite( count ) && count > 0 ? count : 0;
};

/**
* Product Rating Block Component.
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @return {*} The component.
*/
export const Block = ( props ) => {
export const Block = ( props: Props ): JSX.Element | null => {
const { parentClassName } = useInnerBlockLayoutContext();
const { product } = useProductDataContext();
const rating = getAverageRating( product );
Expand Down Expand Up @@ -96,20 +118,4 @@ export const Block = ( props ) => {
);
};

const getAverageRating = ( product ) => {
const rating = parseFloat( product.average_rating );

return Number.isFinite( rating ) && rating > 0 ? rating : 0;
};

const getRatingCount = ( product ) => {
const count = parseInt( product.review_count, 10 );

return Number.isFinite( count ) && count > 0 ? count : 0;
};

Block.propTypes = {
className: PropTypes.string,
};

export default withProductDataContext( Block );
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,40 @@
*/
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { useEffect } from 'react';
import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';

/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
import { Attributes } from './types';

const Edit = ( { attributes } ) => {
const Edit = ( {
attributes,
setAttributes,
context,
}: BlockEditProps< Attributes > & { context: Context } ): JSX.Element => {
const blockProps = useBlockProps( {
className: 'wp-block-woocommerce-product-rating',
} );
const blockAttrs = {
...attributes,
...context,
};
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );

useEffect(
() => setAttributes( { isDescendentOfQueryLoop } ),
[ setAttributes, isDescendentOfQueryLoop ]
);

return (
<div { ...blockProps }>
<Block { ...attributes } />
<Block { ...blockAttrs } />
Aljullu marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import type { BlockConfiguration } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -15,17 +16,21 @@ import {
BLOCK_DESCRIPTION as description,
} from './constants';
import { supports } from './support';
import { Save } from './save';

const blockConfig = {
const blockConfig: BlockConfiguration = {
apiVersion: 2,
title,
description,
usesContext: [ 'query', 'queryId', 'postId' ],
ancestor: [
'@woocommerce/all-products',
'@woocommerce/single-product',
'core/post-template',
],
icon: { src: icon },
attributes,
supports,
edit,
save: Save,
};

registerBlockType( 'woocommerce/product-rating', {
Expand Down
4 changes: 4 additions & 0 deletions assets/js/atomic/blocks/product-elements/rating/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Attributes {
productId: number;
isDescendentOfQueryLoop: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ const withProductSelector = ( selectorArgs ) => ( OriginalComponent ) => {
const { productId } = attributes;
const [ isEditing, setIsEditing ] = useState( ! productId );

if ( productDataContext.hasContext ) {
if (
productDataContext.hasContext ||
Number.isFinite( props.context?.queryId )
) {
return <OriginalComponent { ...props } />;
}

Expand Down
5 changes: 5 additions & 0 deletions assets/js/blocks/product-query/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export interface QueryBlockQuery {
taxQuery?: string;
}

export interface ProductQueryContext {
query?: QueryBlockQuery & ProductQueryArguments;
queryId?: number;
}

export enum QueryVariation {
/** The main, fully customizable, Product Query block */
PRODUCT_QUERY = 'woocommerce/product-query',
Expand Down
4 changes: 4 additions & 0 deletions bin/webpack-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ const getAlias = ( options = {} ) => {
__dirname,
`../assets/js/${ pathPart }base/utils/`
),
'@woocommerce/blocks': path.resolve(
__dirname,
`../assets/js/${ pathPart }/blocks`
),
'@woocommerce/editor-components': path.resolve(
__dirname,
`../assets/js/${ pathPart }editor-components/`
Expand Down
67 changes: 63 additions & 4 deletions src/BlockTypes/ProductRating.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,71 @@ protected function get_block_type_supports() {
}

/**
* Register script and style assets for the block type before it is registered.
* Overwrite parent method to prevent script registration.
*
* This registers the scripts; it does not enqueue them.
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return null;
}

/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}

/**
* Filter the output from wc_get_rating_html.
*
* @param string $html Star rating markup. Default empty string.
* @param float $rating Rating being shown.
* @param int $count Total number of ratings.
* @return string
*/
public function filter_rating_html( $html, $rating, $count ) {
if ( 0 < $rating ) {
/* translators: %s: rating */
$label = sprintf( __( 'Rated %s out of 5', 'woo-gutenberg-products-block' ), $rating );
$html = '<div class="wc-block-components-product-rating__stars wc-block-grid__product-rating__stars" role="img" aria-label="' . esc_attr( $label ) . '">' . wc_get_star_rating_html( $rating, $count ) . '</div>';
}
return $html;
}

/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}

$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );

add_filter(
'woocommerce_product_get_rating_html',
[ $this, 'filter_rating_html' ],
10,
3
);

if ( $product ) {
return sprintf(
'<div class="wc-block-components-product-rating wc-block-grid__product-rating">
%s
</div>',
wc_get_rating_html( $product->get_average_rating() )
);
}
}
}
1 change: 1 addition & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@woocommerce/base-hocs/*": [ "assets/js/base/hocs/*" ],
"@woocommerce/base-hooks": [ "assets/js/base/hooks" ],
"@woocommerce/base-utils": [ "assets/js/base/utils" ],
"@woocommerce/blocks/*": [ "assets/js/blocks/*" ],
"@woocommerce/editor-components/*": [
"assets/js/editor-components/*"
],
Expand Down