diff --git a/assets/js/blocks/product-collection/block.json b/assets/js/blocks/product-collection/block.json index 0da2b5ba0de..bbba8515406 100644 --- a/assets/js/blocks/product-collection/block.json +++ b/assets/js/blocks/product-collection/block.json @@ -28,7 +28,8 @@ "inherit": false, "taxQuery": null, "parents": [], - "isProductCollectionBlock": true + "isProductCollectionBlock": true, + "woocommerceOnSale": false } }, "tagName": { diff --git a/assets/js/blocks/product-collection/inspector-controls/columns-control.tsx b/assets/js/blocks/product-collection/inspector-controls/columns-control.tsx index 85842e0a0e8..7143b82f0ba 100644 --- a/assets/js/blocks/product-collection/inspector-controls/columns-control.tsx +++ b/assets/js/blocks/product-collection/inspector-controls/columns-control.tsx @@ -1,14 +1,23 @@ /** * External dependencies */ -import { RangeControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BlockEditProps } from '@wordpress/blocks'; +import { + RangeControl, + // @ts-expect-error Using experimental features + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; /** * Internal dependencies */ -import { ProductCollectionAttributes } from '../types'; +import { + ProductCollectionAttributes, + ProductCollectionDisplayLayout, +} from '../types'; +import { getDefaultSettings } from './constants'; const ColumnsControl = ( props: BlockEditProps< ProductCollectionAttributes > @@ -16,21 +25,37 @@ const ColumnsControl = ( const { type, columns } = props.attributes.displayLayout; const showColumnsControl = type === 'flex'; + const defaultSettings = getDefaultSettings( props.attributes ); + return showColumnsControl ? ( - - props.setAttributes( { - displayLayout: { - ...props.attributes.displayLayout, - columns: value, - }, - } ) + hasValue={ () => + defaultSettings.displayLayout?.columns !== columns || + defaultSettings.displayLayout?.type !== type } - min={ 2 } - max={ Math.max( 6, columns ) } - /> + isShownByDefault + onDeselect={ () => { + props.setAttributes( { + displayLayout: + defaultSettings.displayLayout as ProductCollectionDisplayLayout, + } ); + } } + > + + props.setAttributes( { + displayLayout: { + ...props.attributes.displayLayout, + columns: value, + }, + } ) + } + min={ 2 } + max={ Math.max( 6, columns ) } + /> + ) : null; }; diff --git a/assets/js/blocks/product-collection/inspector-controls/constants.ts b/assets/js/blocks/product-collection/inspector-controls/constants.ts new file mode 100644 index 00000000000..7748e43a17e --- /dev/null +++ b/assets/js/blocks/product-collection/inspector-controls/constants.ts @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import blockJson from '../block.json'; +import { + ProductCollectionAttributes, + TProductCollectionOrder, + TProductCollectionOrderBy, +} from '../types'; + +const defaultQuery = blockJson.attributes.query.default; + +export const DEFAULT_FILTERS = { + woocommerceOnSale: defaultQuery.woocommerceOnSale, +}; + +export const getDefaultSettings = ( + currentAttributes: ProductCollectionAttributes +): Partial< ProductCollectionAttributes > => ( { + displayLayout: blockJson.attributes.displayLayout.default, + query: { + ...currentAttributes.query, + orderBy: blockJson.attributes.query.default + .orderBy as TProductCollectionOrderBy, + order: blockJson.attributes.query.default + .order as TProductCollectionOrder, + }, +} ); diff --git a/assets/js/blocks/product-collection/inspector-controls/index.tsx b/assets/js/blocks/product-collection/inspector-controls/index.tsx index 2f36e08344b..7b13e23ed9e 100644 --- a/assets/js/blocks/product-collection/inspector-controls/index.tsx +++ b/assets/js/blocks/product-collection/inspector-controls/index.tsx @@ -3,8 +3,12 @@ */ import type { BlockEditProps } from '@wordpress/blocks'; import { InspectorControls } from '@wordpress/block-editor'; -import { PanelBody } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { + // @ts-expect-error Using experimental features + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalToolsPanel as ToolsPanel, +} from '@wordpress/components'; /** * Internal dependencies @@ -12,18 +16,36 @@ import { __ } from '@wordpress/i18n'; import { ProductCollectionAttributes } from '../types'; import ColumnsControl from './columns-control'; import OrderByControl from './order-by-control'; +import OnSaleControl from './on-sale-control'; +import { setQueryAttribute } from './utils'; +import { DEFAULT_FILTERS, getDefaultSettings } from './constants'; const ProductCollectionInspectorControls = ( props: BlockEditProps< ProductCollectionAttributes > ) => { return ( - { + const defaultSettings = getDefaultSettings( + props.attributes + ); + props.setAttributes( defaultSettings ); + } } > - + + + { + setQueryAttribute( props, DEFAULT_FILTERS ); + } } + > + + ); }; diff --git a/assets/js/blocks/product-collection/inspector-controls/on-sale-control.tsx b/assets/js/blocks/product-collection/inspector-controls/on-sale-control.tsx new file mode 100644 index 00000000000..fc9f637e247 --- /dev/null +++ b/assets/js/blocks/product-collection/inspector-controls/on-sale-control.tsx @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { BlockEditProps } from '@wordpress/blocks'; +import { + ToggleControl, + // @ts-expect-error Using experimental features + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { ProductCollectionAttributes } from '../types'; + +const OnSaleControl = ( + props: BlockEditProps< ProductCollectionAttributes > +) => { + const { query } = props.attributes; + + return ( + query.woocommerceOnSale } + isShownByDefault + onDeselect={ () => { + props.setAttributes( { + query: { + ...query, + woocommerceOnSale: false, + }, + } ); + } } + > + { + props.setAttributes( { + query: { + ...query, + woocommerceOnSale, + }, + } ); + } } + /> + + ); +}; + +export default OnSaleControl; diff --git a/assets/js/blocks/product-collection/inspector-controls/order-by-control.tsx b/assets/js/blocks/product-collection/inspector-controls/order-by-control.tsx index 10c130f1a69..56d1178e014 100644 --- a/assets/js/blocks/product-collection/inspector-controls/order-by-control.tsx +++ b/assets/js/blocks/product-collection/inspector-controls/order-by-control.tsx @@ -1,9 +1,14 @@ /** * External dependencies */ -import { SelectControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BlockEditProps } from '@wordpress/blocks'; +import { + SelectControl, + // @ts-expect-error Using experimental features + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; /** * Internal dependencies @@ -13,6 +18,7 @@ import { TProductCollectionOrder, TProductCollectionOrderBy, } from '../types'; +import { getDefaultSettings } from './constants'; const orderOptions = [ { @@ -45,22 +51,40 @@ const OrderByControl = ( props: BlockEditProps< ProductCollectionAttributes > ) => { const { order, orderBy } = props.attributes.query; + const defaultSettings = getDefaultSettings( props.attributes ); + return ( - { - const [ newOrderBy, newOrder ] = value.split( '/' ); + hasValue={ () => + order !== defaultSettings.query?.order || + orderBy !== defaultSettings.query?.orderBy + } + isShownByDefault + onDeselect={ () => { props.setAttributes( { query: { ...props.attributes.query, - order: newOrder as TProductCollectionOrder, - orderBy: newOrderBy as TProductCollectionOrderBy, + ...defaultSettings.query, }, } ); } } - /> + > + { + const [ newOrderBy, newOrder ] = value.split( '/' ); + props.setAttributes( { + query: { + ...props.attributes.query, + order: newOrder as TProductCollectionOrder, + orderBy: newOrderBy as TProductCollectionOrderBy, + }, + } ); + } } + /> + ); }; diff --git a/assets/js/blocks/product-collection/inspector-controls/utils.tsx b/assets/js/blocks/product-collection/inspector-controls/utils.tsx new file mode 100644 index 00000000000..fce65c550c1 --- /dev/null +++ b/assets/js/blocks/product-collection/inspector-controls/utils.tsx @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { BlockEditProps } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { ProductCollectionAttributes, ProductCollectionQuery } from '../types'; + +/** + * Sets the new query arguments of a Product Query block + * + * Shorthand for setting new nested query parameters. + */ +export function setQueryAttribute( + block: BlockEditProps< ProductCollectionAttributes >, + queryParams: Partial< ProductCollectionQuery > +) { + const { query } = block.attributes; + + block.setAttributes( { + query: { + ...query, + ...queryParams, + }, + } ); +} diff --git a/assets/js/blocks/product-collection/types.ts b/assets/js/blocks/product-collection/types.ts index abafef0ecc0..0735268f912 100644 --- a/assets/js/blocks/product-collection/types.ts +++ b/assets/js/blocks/product-collection/types.ts @@ -7,10 +7,12 @@ export interface ProductCollectionAttributes { } ]; templateSlug: string; - displayLayout: { - type: string; - columns: number; - }; + displayLayout: ProductCollectionDisplayLayout; +} + +export interface ProductCollectionDisplayLayout { + type: string; + columns: number; } export interface ProductCollectionQuery { @@ -27,6 +29,7 @@ export interface ProductCollectionQuery { search: string; sticky: string; taxQuery: string; + woocommerceOnSale: boolean; } export type TProductCollectionOrder = 'asc' | 'desc'; diff --git a/src/BlockTypes/ProductCollection.php b/src/BlockTypes/ProductCollection.php index 56898ea7736..03e02e882a7 100644 --- a/src/BlockTypes/ProductCollection.php +++ b/src/BlockTypes/ProductCollection.php @@ -77,10 +77,12 @@ public function update_rest_query( $args, $request ): array { } $orderby = $request->get_param( 'orderby' ); + $on_sale = $request->get_param( 'woocommerceOnSale' ) === 'true'; $orderby_query = $orderby ? $this->get_custom_orderby_query( $orderby ) : []; + $on_sale_query = $this->get_on_sale_products_query( $on_sale ); - return array_merge( $args, $orderby_query ); + return array_merge( $args, $orderby_query, $on_sale_query ); } /** @@ -126,9 +128,12 @@ public function build_query( $query, $block ) { 'tax_query' => array(), ); + $is_on_sale = $block->context['query']['woocommerceOnSale'] ?? false; + $merged_query = $this->merge_queries( $common_query_values, - $this->get_custom_orderby_query( $query['orderby'] ) + $this->get_custom_orderby_query( $query['orderby'] ), + $this->get_on_sale_products_query( $is_on_sale ) ); return $merged_query; @@ -199,6 +204,23 @@ private function get_custom_orderby_query( $orderby ) { ); } + /** + * Return a query for on sale products. + * + * @param bool $is_on_sale Whether to query for on sale products. + * + * @return array + */ + private function get_on_sale_products_query( $is_on_sale ) { + if ( ! $is_on_sale ) { + return array(); + } + + return array( + 'post__in' => wc_get_product_ids_on_sale(), + ); + } + /** * Return or initialize $valid_query_vars. *