This reverts commit 44db8317a7.
This commit is contained in:
Thomas Roberts 2021-08-20 15:44:10 +01:00
parent 44db8317a7
commit 8f2bc114a4
26 changed files with 61 additions and 864 deletions

View File

@ -1,9 +0,0 @@
.wc-filter-element-label-list-count {
&::before {
content: " (";
}
&::after {
content: ")";
}
opacity: 0.6;
}

View File

@ -9,7 +9,10 @@ import PropTypes from 'prop-types';
*/ */
import ProductList from './product-list'; import ProductList from './product-list';
const ProductListContainer = ( { attributes } ) => { const ProductListContainer = ( {
attributes,
hideOutOfStockItems = false,
} ) => {
const [ currentPage, setPage ] = useState( 1 ); const [ currentPage, setPage ] = useState( 1 );
const [ currentSort, setSort ] = useState( attributes.orderby ); const [ currentSort, setSort ] = useState( attributes.orderby );
useEffect( () => { useEffect( () => {
@ -28,6 +31,7 @@ const ProductListContainer = ( { attributes } ) => {
return ( return (
<ProductList <ProductList
attributes={ attributes } attributes={ attributes }
hideOutOfStockItems={ hideOutOfStockItems }
currentPage={ currentPage } currentPage={ currentPage }
onPageChange={ onPageChange } onPageChange={ onPageChange }
onSortChange={ onSortChange } onSortChange={ onSortChange }

View File

@ -27,7 +27,12 @@ import ProductSortSelect from './product-sort-select';
import ProductListItem from './product-list-item'; import ProductListItem from './product-list-item';
import './style.scss'; import './style.scss';
const generateQuery = ( { sortValue, currentPage, attributes } ) => { const generateQuery = ( {
sortValue,
currentPage,
attributes,
hideOutOfStockItems,
} ) => {
const { columns, rows } = attributes; const { columns, rows } = attributes;
const getSortArgs = ( orderName ) => { const getSortArgs = ( orderName ) => {
switch ( orderName ) { switch ( orderName ) {
@ -57,6 +62,9 @@ const generateQuery = ( { sortValue, currentPage, attributes } ) => {
catalog_visibility: 'catalog', catalog_visibility: 'catalog',
per_page: columns * rows, per_page: columns * rows,
page: currentPage, page: currentPage,
...( hideOutOfStockItems && {
stock_status: [ 'instock', 'onbackorder' ],
} ),
}; };
}; };
@ -110,24 +118,14 @@ const ProductList = ( {
onSortChange, onSortChange,
sortValue, sortValue,
scrollToTop, scrollToTop,
hideOutOfStockItems = false,
} ) => { } ) => {
// These are possible filters.
const [ productAttributes, setProductAttributes ] = useQueryStateByKey(
'attributes',
[]
);
const [ productStockStatus, setProductStockStatus ] = useQueryStateByKey(
'stock_status',
[]
);
const [ minPrice, setMinPrice ] = useQueryStateByKey( 'min_price' );
const [ maxPrice, setMaxPrice ] = useQueryStateByKey( 'max_price' );
const [ queryState ] = useSynchronizedQueryState( const [ queryState ] = useSynchronizedQueryState(
generateQuery( { generateQuery( {
attributes, attributes,
sortValue, sortValue,
currentPage, currentPage,
hideOutOfStockItems,
} ) } )
); );
const { products, totalProducts, productsLoading } = useStoreProducts( const { products, totalProducts, productsLoading } = useStoreProducts(
@ -137,6 +135,14 @@ const ProductList = ( {
const totalQuery = extractPaginationAndSortAttributes( queryState ); const totalQuery = extractPaginationAndSortAttributes( queryState );
const { dispatchStoreEvent } = useStoreEvents(); const { dispatchStoreEvent } = useStoreEvents();
// These are possible filters.
const [ productAttributes, setProductAttributes ] = useQueryStateByKey(
'attributes',
[]
);
const [ minPrice, setMinPrice ] = useQueryStateByKey( 'min_price' );
const [ maxPrice, setMaxPrice ] = useQueryStateByKey( 'max_price' );
// Only update previous query totals if the query is different and the total number of products is a finite number. // Only update previous query totals if the query is different and the total number of products is a finite number.
const previousQueryTotals = usePrevious( const previousQueryTotals = usePrevious(
{ totalQuery, totalProducts }, { totalQuery, totalProducts },
@ -203,7 +209,6 @@ const ProductList = ( {
const hasProducts = products.length !== 0 || productsLoading; const hasProducts = products.length !== 0 || productsLoading;
const hasFilters = const hasFilters =
productAttributes.length > 0 || productAttributes.length > 0 ||
productStockStatus.length > 0 ||
Number.isFinite( minPrice ) || Number.isFinite( minPrice ) ||
Number.isFinite( maxPrice ); Number.isFinite( maxPrice );
@ -219,7 +224,6 @@ const ProductList = ( {
<NoMatchingProducts <NoMatchingProducts
resetCallback={ () => { resetCallback={ () => {
setProductAttributes( [] ); setProductAttributes( [] );
setProductStockStatus( [] );
setMinPrice( null ); setMinPrice( null );
setMaxPrice( null ); setMaxPrice( null );
} } } }
@ -250,6 +254,7 @@ const ProductList = ( {
ProductList.propTypes = { ProductList.propTypes = {
attributes: PropTypes.object.isRequired, attributes: PropTypes.object.isRequired,
hideOutOfStockItems: PropTypes.bool,
// From withScrollToTop. // From withScrollToTop.
scrollToTop: PropTypes.func, scrollToTop: PropTypes.func,
}; };

View File

@ -36,7 +36,6 @@ const buildCollectionDataQuery = ( collectionDataQueryState ) => {
export const useCollectionData = ( { export const useCollectionData = ( {
queryAttribute, queryAttribute,
queryPrices, queryPrices,
queryStock,
queryState, queryState,
} ) => { } ) => {
let context = useQueryStateContext(); let context = useQueryStateContext();
@ -51,14 +50,9 @@ export const useCollectionData = ( {
calculatePriceRangeQueryState, calculatePriceRangeQueryState,
setCalculatePriceRangeQueryState, setCalculatePriceRangeQueryState,
] = useQueryStateByKey( 'calculate_price_range', null, context ); ] = useQueryStateByKey( 'calculate_price_range', null, context );
const [
calculateStockStatusQueryState,
setCalculateStockStatusQueryState,
] = useQueryStateByKey( 'calculate_stock_status_counts', null, context );
const currentQueryAttribute = useShallowEqual( queryAttribute || {} ); const currentQueryAttribute = useShallowEqual( queryAttribute || {} );
const currentQueryPrices = useShallowEqual( queryPrices ); const currentQueryPrices = useShallowEqual( queryPrices );
const currentQueryStock = useShallowEqual( queryStock );
useEffect( () => { useEffect( () => {
if ( if (
@ -99,19 +93,6 @@ export const useCollectionData = ( {
calculatePriceRangeQueryState, calculatePriceRangeQueryState,
] ); ] );
useEffect( () => {
if (
calculateStockStatusQueryState !== currentQueryStock &&
currentQueryStock !== undefined
) {
setCalculateStockStatusQueryState( currentQueryStock );
}
}, [
currentQueryStock,
setCalculateStockStatusQueryState,
calculateStockStatusQueryState,
] );
// Defer the select query so all collection-data query vars can be gathered. // Defer the select query so all collection-data query vars can be gathered.
const [ shouldSelect, setShouldSelect ] = useState( false ); const [ shouldSelect, setShouldSelect ] = useState( false );
const [ debouncedShouldSelect ] = useDebounce( shouldSelect, 200 ); const [ debouncedShouldSelect ] = useDebounce( shouldSelect, 200 );

View File

@ -3,7 +3,6 @@
*/ */
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { useQueryStateByKey } from '@woocommerce/base-context/hooks'; import { useQueryStateByKey } from '@woocommerce/base-context/hooks';
import { getSetting } from '@woocommerce/settings';
import { useMemo } from '@wordpress/element'; import { useMemo } from '@wordpress/element';
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -32,39 +31,9 @@ const ActiveFiltersBlock = ( {
'attributes', 'attributes',
[] []
); );
const [ productStockStatus, setProductStockStatus ] = useQueryStateByKey(
'stock_status',
[]
);
const [ minPrice, setMinPrice ] = useQueryStateByKey( 'min_price' ); const [ minPrice, setMinPrice ] = useQueryStateByKey( 'min_price' );
const [ maxPrice, setMaxPrice ] = useQueryStateByKey( 'max_price' ); const [ maxPrice, setMaxPrice ] = useQueryStateByKey( 'max_price' );
const STOCK_STATUS_OPTIONS = getSetting( 'stockStatusOptions', [] );
const activeStockStatusFilters = useMemo( () => {
if ( productStockStatus.length > 0 ) {
return productStockStatus.map( ( slug ) => {
return renderRemovableListItem( {
type: __( 'Stock Status', 'woo-gutenberg-products-block' ),
name: STOCK_STATUS_OPTIONS[ slug ],
removeCallback: () => {
const newStatuses = productStockStatus.filter(
( status ) => {
return status !== slug;
}
);
setProductStockStatus( newStatuses );
},
displayStyle: blockAttributes.displayStyle,
} );
} );
}
}, [
STOCK_STATUS_OPTIONS,
productStockStatus,
setProductStockStatus,
blockAttributes.displayStyle,
] );
const activePriceFilters = useMemo( () => { const activePriceFilters = useMemo( () => {
if ( ! Number.isFinite( minPrice ) && ! Number.isFinite( maxPrice ) ) { if ( ! Number.isFinite( minPrice ) && ! Number.isFinite( maxPrice ) ) {
return null; return null;
@ -106,7 +75,6 @@ const ActiveFiltersBlock = ( {
const hasFilters = () => { const hasFilters = () => {
return ( return (
productAttributes.length > 0 || productAttributes.length > 0 ||
productStockStatus.length > 0 ||
Number.isFinite( minPrice ) || Number.isFinite( minPrice ) ||
Number.isFinite( maxPrice ) Number.isFinite( maxPrice )
); );
@ -157,7 +125,6 @@ const ActiveFiltersBlock = ( {
) : ( ) : (
<> <>
{ activePriceFilters } { activePriceFilters }
{ activeStockStatusFilters }
{ activeAttributeFilters } { activeAttributeFilters }
</> </>
) } ) }
@ -168,7 +135,6 @@ const ActiveFiltersBlock = ( {
setMinPrice( undefined ); setMinPrice( undefined );
setMaxPrice( undefined ); setMaxPrice( undefined );
setProductAttributes( [] ); setProductAttributes( [] );
setProductStockStatus( [] );
} } } }
> >
<Label <Label

View File

@ -13,7 +13,6 @@ import {
import { useCallback, useEffect, useState, useMemo } from '@wordpress/element'; import { useCallback, useEffect, useState, useMemo } from '@wordpress/element';
import CheckboxList from '@woocommerce/base-components/checkbox-list'; import CheckboxList from '@woocommerce/base-components/checkbox-list';
import DropdownSelector from '@woocommerce/base-components/dropdown-selector'; import DropdownSelector from '@woocommerce/base-components/dropdown-selector';
import Label from '@woocommerce/base-components/filter-element-label';
import FilterSubmitButton from '@woocommerce/base-components/filter-submit-button'; import FilterSubmitButton from '@woocommerce/base-components/filter-submit-button';
import isShallowEqual from '@wordpress/is-shallow-equal'; import isShallowEqual from '@wordpress/is-shallow-equal';
import { decodeEntities } from '@wordpress/html-entities'; import { decodeEntities } from '@wordpress/html-entities';
@ -23,6 +22,7 @@ import { decodeEntities } from '@wordpress/html-entities';
*/ */
import { getAttributeFromID } from '../../utils/attributes'; import { getAttributeFromID } from '../../utils/attributes';
import { updateAttributeFilter } from '../../utils/attributes-query'; import { updateAttributeFilter } from '../../utils/attributes-query';
import Label from './label';
import { previewAttributeObject, previewOptions } from './preview'; import { previewAttributeObject, previewOptions } from './preview';
import './style.scss'; import './style.scss';

View File

@ -5,18 +5,13 @@ import { _n, sprintf } from '@wordpress/i18n';
import Label from '@woocommerce/base-components/label'; import Label from '@woocommerce/base-components/label';
/** /**
* Internal dependencies * The label for an attribute term filter.
*/
import './style.scss';
/**
* The label for an filter elements.
* *
* @param {Object} props Incoming props for the component. * @param {Object} props Incoming props for the component.
* @param {string} props.name The name for the label. * @param {string} props.name The name for the label.
* @param {number} props.count The count of products this status is attached to. * @param {number} props.count The count of products this attribute is attached to.
*/ */
const FilterElementLabel = ( { name, count } ) => { const AttributeFilterLabel = ( { name, count } ) => {
return ( return (
<> <>
{ name } { name }
@ -35,7 +30,7 @@ const FilterElementLabel = ( { name, count } ) => {
) } ) }
wrapperElement="span" wrapperElement="span"
wrapperProps={ { wrapperProps={ {
className: 'wc-filter-element-label-list-count', className: 'wc-block-attribute-filter-list-count',
} } } }
/> />
) } ) }
@ -43,4 +38,4 @@ const FilterElementLabel = ( { name, count } ) => {
); );
}; };
export default FilterElementLabel; export default AttributeFilterLabel;

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * Internal dependencies
*/ */
import Label from '@woocommerce/base-components/filter-element-label'; import Label from './label';
export const previewOptions = [ export const previewOptions = [
{ {

View File

@ -1,6 +1,15 @@
.wc-block-attribute-filter { .wc-block-attribute-filter {
margin-bottom: $gap-large; margin-bottom: $gap-large;
.wc-block-attribute-filter-list-count {
&::before {
content: " (";
}
&::after {
content: ")";
}
}
.wc-block-attribute-filter-list { .wc-block-attribute-filter-list {
margin: 0; margin: 0;
@ -16,6 +25,10 @@
display: inline-block; display: inline-block;
} }
} }
.wc-block-attribute-filter-list-count {
float: right;
}
} }
.is-single .wc-block-attribute-filter-list-count, .is-single .wc-block-attribute-filter-list-count,

View File

@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import { ProductListContainer } from '@woocommerce/base-components/product-list'; import { ProductListContainer } from '@woocommerce/base-components/product-list';
import { InnerBlockLayoutContextProvider } from '@woocommerce/shared-context'; import { InnerBlockLayoutContextProvider } from '@woocommerce/shared-context';
import { gridBlockPreview } from '@woocommerce/resource-previews'; import { gridBlockPreview } from '@woocommerce/resource-previews';
import { getSetting } from '@woocommerce/settings';
/** /**
* The All Products Block. * The All Products Block.
@ -25,6 +26,8 @@ class Block extends Component {
return gridBlockPreview; return gridBlockPreview;
} }
const hideOutOfStockItems = getSetting( 'hideOutOfStockItems', false );
/** /**
* Todo classes * Todo classes
* *
@ -39,6 +42,7 @@ class Block extends Component {
<ProductListContainer <ProductListContainer
attributes={ attributes } attributes={ attributes }
urlParameterSuffix={ urlParameterSuffix } urlParameterSuffix={ urlParameterSuffix }
hideOutOfStockItems={ hideOutOfStockItems }
/> />
</InnerBlockLayoutContextProvider> </InnerBlockLayoutContextProvider>
); );

View File

@ -1,278 +0,0 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { speak } from '@wordpress/a11y';
import { usePrevious, useShallowEqual } from '@woocommerce/base-hooks';
import {
useQueryStateByKey,
useQueryStateByContext,
useCollectionData,
} from '@woocommerce/base-context/hooks';
import { getSetting } from '@woocommerce/settings';
import { useCallback, useEffect, useState, useMemo } from '@wordpress/element';
import CheckboxList from '@woocommerce/base-components/checkbox-list';
import FilterSubmitButton from '@woocommerce/base-components/filter-submit-button';
import Label from '@woocommerce/base-components/filter-element-label';
import isShallowEqual from '@wordpress/is-shallow-equal';
import { decodeEntities } from '@wordpress/html-entities';
/**
* Internal dependencies
*/
import { previewOptions } from './preview';
import './style.scss';
const hideOutOfStockItems = getSetting( 'hideOutOfStockItems', false );
const { outofstock, ...otherStockStatusOptions } = getSetting(
'stockStatusOptions',
{}
);
const STOCK_STATUS_OPTIONS = hideOutOfStockItems
? otherStockStatusOptions
: { outofstock, ...otherStockStatusOptions };
// Filter added to handle if there are slugs without a corresponding name defined.
const initialOptions = Object.entries( STOCK_STATUS_OPTIONS )
.map( ( [ slug, name ] ) => ( { slug, name } ) )
.filter( ( status ) => !! status.name )
.sort( ( a, b ) => a.slug.localeCompare( b.slug ) );
/**
* Component displaying an stock status filter.
*
* @param {Object} props Incoming props for the component.
* @param {Object} props.attributes Incoming block attributes.
* @param {boolean} props.isEditor
*/
const StockStatusFilterBlock = ( {
attributes: blockAttributes,
isEditor = false,
} ) => {
const [ checked, setChecked ] = useState( [] );
const [ displayedOptions, setDisplayedOptions ] = useState(
blockAttributes.isPreview ? previewOptions : []
);
const [ queryState ] = useQueryStateByContext();
const [
productStockStatusQuery,
setProductStockStatusQuery,
] = useQueryStateByKey( 'stock_status', [] );
const {
results: filteredCounts,
isLoading: filteredCountsLoading,
} = useCollectionData( {
queryStock: true,
queryState,
} );
/**
* Get count data about a given status by slug.
*/
const getFilteredStock = useCallback(
( slug ) => {
if ( ! filteredCounts.stock_status_counts ) {
return null;
}
return filteredCounts.stock_status_counts.find(
( { status, count } ) =>
status === slug && Number( count ) !== 0
);
},
[ filteredCounts ]
);
/**
* Compare intersection of all stock statuses and filtered counts to get a list of options to display.
*/
useEffect( () => {
/**
* Checks if a status slug is in the query state.
*
* @param {string} queryStatus The status slug to check.
*/
const isStockStatusInQueryState = ( queryStatus ) => {
if ( ! queryState?.stock_status ) {
return false;
}
return queryState.stock_status.some( ( { status = [] } ) =>
status.includes( queryStatus )
);
};
if ( filteredCountsLoading || blockAttributes.isPreview ) {
return;
}
const newOptions = initialOptions
.map( ( status ) => {
const filteredStock = getFilteredStock( status.slug );
if (
! filteredStock &&
! checked.includes( status.slug ) &&
! isStockStatusInQueryState( status.slug )
) {
return null;
}
const count = filteredStock ? Number( filteredStock.count ) : 0;
return {
value: status.slug,
name: decodeEntities( status.name ),
label: (
<Label
name={ decodeEntities( status.name ) }
count={ blockAttributes.showCounts ? count : null }
/>
),
};
} )
.filter( Boolean );
setDisplayedOptions( newOptions );
}, [
blockAttributes.showCounts,
blockAttributes.isPreview,
filteredCountsLoading,
getFilteredStock,
checked,
queryState.stock_status,
] );
const onSubmit = useCallback(
( isChecked ) => {
if ( isEditor ) {
return;
}
if ( isChecked ) {
setProductStockStatusQuery( checked );
}
},
[ isEditor, setProductStockStatusQuery, checked ]
);
// Track checked STATE changes - if state changes, update the query.
useEffect( () => {
if ( ! blockAttributes.showFilterButton ) {
onSubmit( checked );
}
}, [ blockAttributes.showFilterButton, checked, onSubmit ] );
const checkedQuery = useMemo( () => {
return productStockStatusQuery;
}, [ productStockStatusQuery ] );
const currentCheckedQuery = useShallowEqual( checkedQuery );
const previousCheckedQuery = usePrevious( currentCheckedQuery );
// Track Stock query changes so the block reflects current filters.
useEffect( () => {
if (
! isShallowEqual( previousCheckedQuery, currentCheckedQuery ) && // Checked query changed.
! isShallowEqual( checked, currentCheckedQuery ) // Checked query doesn't match the UI.
) {
setChecked( currentCheckedQuery );
}
}, [ checked, currentCheckedQuery, previousCheckedQuery ] );
/**
* When a checkbox in the list changes, update state.
*/
const onChange = useCallback(
( checkedValue ) => {
const getFilterNameFromValue = ( filterValue ) => {
const { name } = displayedOptions.find(
( option ) => option.value === filterValue
);
return name;
};
const announceFilterChange = ( { filterAdded, filterRemoved } ) => {
const filterAddedName = filterAdded
? getFilterNameFromValue( filterAdded )
: null;
const filterRemovedName = filterRemoved
? getFilterNameFromValue( filterRemoved )
: null;
if ( filterAddedName ) {
speak(
sprintf(
/* translators: %s stock statuses (for example: 'instock'...) */
__(
'%s filter added.',
'woo-gutenberg-products-block'
),
filterAddedName
)
);
} else if ( filterRemovedName ) {
speak(
sprintf(
/* translators: %s stock statuses (for example:'instock'...) */
__(
'%s filter removed.',
'woo-gutenberg-products-block'
),
filterRemovedName
)
);
}
};
const previouslyChecked = checked.includes( checkedValue );
const newChecked = checked.filter(
( value ) => value !== checkedValue
);
if ( ! previouslyChecked ) {
newChecked.push( checkedValue );
newChecked.sort();
announceFilterChange( { filterAdded: checkedValue } );
} else {
announceFilterChange( { filterRemoved: checkedValue } );
}
setChecked( newChecked );
},
[ checked, displayedOptions ]
);
if ( displayedOptions.length === 0 ) {
return null;
}
const TagName = `h${ blockAttributes.headingLevel }`;
const isLoading = ! blockAttributes.isPreview && ! STOCK_STATUS_OPTIONS;
const isDisabled = ! blockAttributes.isPreview && filteredCountsLoading;
return (
<>
{ ! isEditor && blockAttributes.heading && (
<TagName>{ blockAttributes.heading }</TagName>
) }
<div className="wc-block-stock-filter">
<CheckboxList
className={ 'wc-block-stock-filter-list' }
options={ displayedOptions }
checked={ checked }
onChange={ onChange }
isLoading={ isLoading }
isDisabled={ isDisabled }
/>
{ blockAttributes.showFilterButton && (
<FilterSubmitButton
className="wc-block-stock-filter__button"
disabled={ isLoading || isDisabled }
onClick={ () => onSubmit( checked ) }
/>
) }
</div>
</>
);
};
export default StockStatusFilterBlock;

View File

@ -1,130 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { InspectorControls } from '@wordpress/block-editor';
import {
Disabled,
PanelBody,
ToggleControl,
withSpokenMessages,
} from '@wordpress/components';
import HeadingToolbar from '@woocommerce/editor-components/heading-toolbar';
import BlockTitle from '@woocommerce/editor-components/block-title';
/**
* Internal dependencies
*/
import Block from './block.js';
import './editor.scss';
const Edit = ( { attributes, setAttributes } ) => {
const {
className,
heading,
headingLevel,
showCounts,
showFilterButton,
} = attributes;
const getInspectorControls = () => {
return (
<InspectorControls key="inspector">
<PanelBody
title={ __( 'Content', 'woo-gutenberg-products-block' ) }
>
<ToggleControl
label={ __(
'Product count',
'woo-gutenberg-products-block'
) }
help={
showCounts
? __(
'Product count is visible.',
'woo-gutenberg-products-block'
)
: __(
'Product count is hidden.',
'woo-gutenberg-products-block'
)
}
checked={ showCounts }
onChange={ () =>
setAttributes( {
showCounts: ! showCounts,
} )
}
/>
<p>
{ __(
'Heading Level',
'woo-gutenberg-products-block'
) }
</p>
<HeadingToolbar
isCollapsed={ false }
minLevel={ 2 }
maxLevel={ 7 }
selectedLevel={ headingLevel }
onChange={ ( newLevel ) =>
setAttributes( { headingLevel: newLevel } )
}
/>
</PanelBody>
<PanelBody
title={ __(
'Block Settings',
'woo-gutenberg-products-block'
) }
>
<ToggleControl
label={ __(
'Filter button',
'woo-gutenberg-products-block'
) }
help={
showFilterButton
? __(
'Products will only update when the button is pressed.',
'woo-gutenberg-products-block'
)
: __(
'Products will update as options are selected.',
'woo-gutenberg-products-block'
)
}
checked={ showFilterButton }
onChange={ ( value ) =>
setAttributes( {
showFilterButton: value,
} )
}
/>
</PanelBody>
</InspectorControls>
);
};
return (
<>
{ getInspectorControls() }
{
<div className={ className }>
<BlockTitle
headingLevel={ headingLevel }
heading={ heading }
onChange={ ( value ) =>
setAttributes( { heading: value } )
}
/>
<Disabled>
<Block attributes={ attributes } isEditor={ true } />
</Disabled>
</div>
}
</>
);
};
export default withSpokenMessages( Edit );

View File

@ -1,36 +0,0 @@
.wc-block-stock-filter {
.components-placeholder__instructions {
border-bottom: 1px solid #e0e2e6;
width: 100%;
padding-bottom: 1em;
margin-bottom: 2em;
}
.components-placeholder__label svg {
fill: currentColor;
margin-right: 1ch;
}
.components-placeholder__fieldset {
display: block; /* Disable flex box */
}
.woocommerce-search-list__search {
border-top: 0;
margin-top: 0;
padding-top: 0;
}
.wc-block-stock-filter__add-stock-button {
margin: 0 0 1em;
vertical-align: middle;
height: auto;
padding: 0.5em 1em;
svg {
fill: currentColor;
margin-left: 0.5ch;
vertical-align: middle;
}
}
.wc-block-stock-filter__read_more_button {
display: block;
margin-bottom: 1em;
}
}

View File

@ -1,27 +0,0 @@
/**
* External dependencies
*/
import { withRestApiHydration } from '@woocommerce/block-hocs';
import { renderFrontend } from '@woocommerce/base-utils';
/**
* Internal dependencies
*/
import Block from './block.js';
const getProps = ( el ) => {
return {
attributes: {
showCounts: el.dataset.showCounts === 'true',
heading: el.dataset.heading,
headingLevel: el.dataset.headingLevel || 3,
showFilterButton: el.dataset.showFilterButton === 'true',
},
};
};
renderFrontend( {
selector: '.wp-block-woocommerce-stock-filter',
Block: withRestApiHydration( Block ),
getProps,
} );

View File

@ -1,93 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';
import { Icon, server } from '@woocommerce/icons';
import classNames from 'classnames';
/**
* Internal dependencies
*/
import edit from './edit.js';
registerBlockType( 'woocommerce/stock-filter', {
title: __( 'Filter Products by Stock', 'woo-gutenberg-products-block' ),
icon: {
src: <Icon srcElement={ server } />,
foreground: '#96588a',
},
category: 'woocommerce',
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
description: __(
'Allow customers to filter the grid by products stock status. Works in combination with the All Products block.',
'woo-gutenberg-products-block'
),
supports: {
html: false,
multiple: false,
},
example: {
attributes: {
isPreview: true,
},
},
attributes: {
heading: {
type: 'string',
default: __(
'Filter by stock status',
'woo-gutenberg-products-block'
),
},
headingLevel: {
type: 'number',
default: 3,
},
showCounts: {
type: 'boolean',
default: true,
},
showFilterButton: {
type: 'boolean',
default: false,
},
/**
* Are we previewing?
*/
isPreview: {
type: 'boolean',
default: false,
},
},
edit,
// Save the props to post content.
save( { attributes } ) {
const {
className,
showCounts,
heading,
headingLevel,
showFilterButton,
} = attributes;
const data = {
'data-show-counts': showCounts,
'data-heading': heading,
'data-heading-level': headingLevel,
};
if ( showFilterButton ) {
data[ 'data-show-filter-button' ] = showFilterButton;
}
return (
<div
className={ classNames( 'is-loading', className ) }
{ ...data }
>
<span
aria-hidden
className="wc-block-product-stock-filter__placeholder"
/>
</div>
);
},
} );

View File

@ -1,22 +0,0 @@
/**
* External dependencies
*/
import Label from '@woocommerce/base-components/filter-element-label';
export const previewOptions = [
{
value: 'preview-1',
name: 'In Stock',
label: <Label name="In Stock" count={ 3 } />,
},
{
value: 'preview-2',
name: 'Out of sotck',
label: <Label name="Out of stock" count={ 3 } />,
},
{
value: 'preview-3',
name: 'On backorder',
label: <Label name="On backorder" count={ 2 } />,
},
];

View File

@ -1,29 +0,0 @@
.wc-block-stock-filter {
margin-bottom: $gap-large;
.wc-block-stock-filter-list {
margin: 0;
li {
text-decoration: underline;
label {
cursor: pointer;
}
input {
cursor: pointer;
display: inline-block;
}
}
}
.is-single,
.wc-block-dropdown-selector .wc-block-dropdown-selector__list {
opacity: 0.6;
}
.wc-block-stock-filter__button {
margin-top: $gap-smaller;
}
}

View File

@ -38,7 +38,6 @@ const blocks = {
}, },
'price-filter': {}, 'price-filter': {},
'attribute-filter': {}, 'attribute-filter': {},
'stock-filter': {},
'active-filters': {}, 'active-filters': {},
cart: { cart: {
customDir: 'cart-checkout/cart', customDir: 'cart-checkout/cart',

View File

@ -27,5 +27,6 @@ class AllProducts extends AbstractBlock {
$this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true ); $this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
$this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true ); $this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
$this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true ); $this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
$this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ), true );
} }
} }

View File

@ -1,28 +0,0 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* AttributeFilter class.
*/
class StockFilter extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'stock-filter';
/**
* Extra data passed through from server to client for block.
*
* @param array $stock_statuses Any stock statuses that currently are available from the block.
* Note, this will be empty in the editor context when the block is
* not in the post content on editor load.
*/
protected function enqueue_data( array $stock_statuses = [] ) {
parent::enqueue_data( $stock_statuses );
$this->asset_data_registry->add( 'stockStatusOptions', wc_get_product_stock_status_options(), true );
$this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ), true );
}
}

View File

@ -116,7 +116,6 @@ final class BlockTypesController {
'AllProducts', 'AllProducts',
'PriceFilter', 'PriceFilter',
'AttributeFilter', 'AttributeFilter',
'StockFilter',
'ActiveFilters', 'ActiveFilters',
]; ];
@ -140,7 +139,6 @@ final class BlockTypesController {
'AllProducts', 'AllProducts',
'PriceFilter', 'PriceFilter',
'AttributeFilter', 'AttributeFilter',
'StockFilter',
'ActiveFilters', 'ActiveFilters',
'Cart', 'Cart',
'Checkout', 'Checkout',

View File

@ -47,11 +47,10 @@ class ProductCollectionData extends AbstractRoute {
*/ */
protected function get_route_response( \WP_REST_Request $request ) { protected function get_route_response( \WP_REST_Request $request ) {
$data = [ $data = [
'min_price' => null, 'min_price' => null,
'max_price' => null, 'max_price' => null,
'attribute_counts' => null, 'attribute_counts' => null,
'stock_status_counts' => null, 'rating_counts' => null,
'rating_counts' => null,
]; ];
$filters = new ProductQueryFilters(); $filters = new ProductQueryFilters();
@ -65,20 +64,6 @@ class ProductCollectionData extends AbstractRoute {
$data['max_price'] = $price_results->max_price; $data['max_price'] = $price_results->max_price;
} }
if ( ! empty( $request['calculate_stock_status_counts'] ) ) {
$filter_request = clone $request;
$counts = $filters->get_stock_status_counts( $filter_request );
$data['stock_status_counts'] = [];
foreach ( $counts as $key => $value ) {
$data['stock_status_counts'][] = (object) [
'status' => $key,
'count' => $value,
];
}
}
if ( ! empty( $request['calculate_attribute_counts'] ) ) { if ( ! empty( $request['calculate_attribute_counts'] ) ) {
$taxonomy__or_queries = []; $taxonomy__or_queries = [];
$taxonomy__and_queries = []; $taxonomy__and_queries = [];
@ -163,12 +148,6 @@ class ProductCollectionData extends AbstractRoute {
'default' => false, 'default' => false,
]; ];
$params['calculate_stock_status_counts'] = [
'description' => __( 'If true, calculates stock counts for products in the collection.', 'woo-gutenberg-products-block' ),
'type' => 'boolean',
'default' => false,
];
$params['calculate_attribute_counts'] = [ $params['calculate_attribute_counts'] = [
'description' => __( 'If requested, calculates attribute term counts for products in the collection.', 'woo-gutenberg-products-block' ), 'description' => __( 'If requested, calculates attribute term counts for products in the collection.', 'woo-gutenberg-products-block' ),
'type' => 'array', 'type' => 'array',

View File

@ -29,7 +29,7 @@ class ProductCollectionDataSchema extends AbstractSchema {
*/ */
public function get_properties() { public function get_properties() {
return [ return [
'price_range' => [ 'price_range' => [
'description' => __( 'Min and max prices found in collection of products, provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ), 'description' => __( 'Min and max prices found in collection of products, provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ),
'type' => [ 'object', 'null' ], 'type' => [ 'object', 'null' ],
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
@ -52,7 +52,7 @@ class ProductCollectionDataSchema extends AbstractSchema {
] ]
), ),
], ],
'attribute_counts' => [ 'attribute_counts' => [
'description' => __( 'Returns number of products within attribute terms.', 'woo-gutenberg-products-block' ), 'description' => __( 'Returns number of products within attribute terms.', 'woo-gutenberg-products-block' ),
'type' => [ 'array', 'null' ], 'type' => [ 'array', 'null' ],
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
@ -75,7 +75,7 @@ class ProductCollectionDataSchema extends AbstractSchema {
], ],
], ],
], ],
'rating_counts' => [ 'rating_counts' => [
'description' => __( 'Returns number of products with each average rating.', 'woo-gutenberg-products-block' ), 'description' => __( 'Returns number of products with each average rating.', 'woo-gutenberg-products-block' ),
'type' => [ 'array', 'null' ], 'type' => [ 'array', 'null' ],
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
@ -98,29 +98,6 @@ class ProductCollectionDataSchema extends AbstractSchema {
], ],
], ],
], ],
'stock_status_counts' => [
'description' => __( 'Returns number of products with each stock status.', 'woo-gutenberg-products-block' ),
'type' => [ 'array', 'null' ],
'context' => [ 'view', 'edit' ],
'readonly' => true,
'items' => [
'type' => 'object',
'properties' => [
'status' => [
'description' => __( 'Status', 'woo-gutenberg-products-block' ),
'type' => 'string',
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
'count' => [
'description' => __( 'Number of products.', 'woo-gutenberg-products-block' ),
'type' => 'integer',
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
],
],
],
]; ];
} }
@ -132,15 +109,14 @@ class ProductCollectionDataSchema extends AbstractSchema {
*/ */
public function get_item_response( $data ) { public function get_item_response( $data ) {
return [ return [
'price_range' => ! is_null( $data['min_price'] ) && ! is_null( $data['max_price'] ) ? (object) $this->prepare_currency_response( 'price_range' => ! is_null( $data['min_price'] ) && ! is_null( $data['max_price'] ) ? (object) $this->prepare_currency_response(
[ [
'min_price' => $this->prepare_money_response( $data['min_price'], wc_get_price_decimals() ), 'min_price' => $this->prepare_money_response( $data['min_price'], wc_get_price_decimals() ),
'max_price' => $this->prepare_money_response( $data['max_price'], wc_get_price_decimals() ), 'max_price' => $this->prepare_money_response( $data['max_price'], wc_get_price_decimals() ),
] ]
) : null, ) : null,
'attribute_counts' => $data['attribute_counts'], 'attribute_counts' => $data['attribute_counts'],
'rating_counts' => $data['rating_counts'], 'rating_counts' => $data['rating_counts'],
'stock_status_counts' => $data['stock_status_counts'],
]; ];
} }
} }

View File

@ -319,9 +319,6 @@ class ProductQuery {
if ( $wp_query->get( 'stock_status' ) ) { if ( $wp_query->get( 'stock_status' ) ) {
$args['join'] = $this->append_product_sorting_table_join( $args['join'] ); $args['join'] = $this->append_product_sorting_table_join( $args['join'] );
$args['where'] .= ' AND wc_product_meta_lookup.stock_status IN ("' . implode( '","', array_map( 'esc_sql', $wp_query->get( 'stock_status' ) ) ) . '")'; $args['where'] .= ' AND wc_product_meta_lookup.stock_status IN ("' . implode( '","', array_map( 'esc_sql', $wp_query->get( 'stock_status' ) ) ) . '")';
} elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$args['join'] = $this->append_product_sorting_table_join( $args['join'] );
$args['where'] .= ' AND wc_product_meta_lookup.stock_status NOT IN ("outofstock")';
} }
if ( $wp_query->get( 'min_price' ) || $wp_query->get( 'max_price' ) ) { if ( $wp_query->get( 'min_price' ) || $wp_query->get( 'max_price' ) ) {

View File

@ -47,72 +47,6 @@ class ProductQueryFilters {
return $wpdb->get_row( $price_filter_sql ); // phpcs:ignore return $wpdb->get_row( $price_filter_sql ); // phpcs:ignore
} }
/**
* Get stock status counts for the current products.
*
* @param \WP_REST_Request $request The request object.
* @return array status=>count pairs.
*/
public function get_stock_status_counts( $request ) {
global $wpdb;
$product_query = new ProductQuery();
$stock_status_options = array_map( 'esc_sql', array_keys( wc_get_product_stock_status_options() ) );
$hide_outofstock_items = get_option( 'woocommerce_hide_out_of_stock_items' );
if ( 'yes' === $hide_outofstock_items ) {
unset( $stock_status_options['outofstock'] );
}
add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 );
add_filter( 'posts_pre_query', '__return_empty_array' );
$query_args = $product_query->prepare_objects_query( $request );
unset( $query_args['stock_status'] );
$query_args['no_found_rows'] = true;
$query_args['posts_per_page'] = -1;
$query = new \WP_Query();
$result = $query->query( $query_args );
$product_query_sql = $query->request;
remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 );
remove_filter( 'posts_pre_query', '__return_empty_array' );
$stock_status_counts = array();
foreach ( $stock_status_options as $status ) {
$stock_status_count_sql = $this->generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options );
$result = $wpdb->get_row( $stock_status_count_sql ); // phpcs:ignore
$stock_status_counts[ $status ] = $result->status_count;
}
return $stock_status_counts;
}
/**
* Generate calculate query by stock status.
*
* @param string $status status to calculate.
* @param string $product_query_sql product query for current filter state.
* @param array $stock_status_options available stock status options.
*
* @return false|string
*/
private function generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ) {
if ( ! in_array( $status, $stock_status_options, true ) ) {
return false;
}
global $wpdb;
$status = esc_sql( $status );
return "
SELECT COUNT( DISTINCT posts.ID ) as status_count
FROM {$wpdb->posts} as posts
INNER JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id
AND postmeta.meta_key = '_stock_status'
AND postmeta.meta_value = '{$status}'
WHERE posts.ID IN ( {$product_query_sql} )
";
}
/** /**
* Get attribute counts for the current products. * Get attribute counts for the current products.
* *

View File

@ -100,7 +100,6 @@ const LegacyMainConfig = {
'all-products', 'all-products',
'price-filter', 'price-filter',
'attribute-filter', 'attribute-filter',
'stock-filter',
'active-filters', 'active-filters',
'checkout', 'checkout',
'cart', 'cart',
@ -124,7 +123,6 @@ const LegacyFrontendConfig = {
'all-products', 'all-products',
'price-filter', 'price-filter',
'attribute-filter', 'attribute-filter',
'stock-filter',
'active-filters', 'active-filters',
'checkout', 'checkout',
'cart', 'cart',
@ -141,7 +139,6 @@ const LegacyStylingConfig = {
'all-products', 'all-products',
'price-filter', 'price-filter',
'attribute-filter', 'attribute-filter',
'stock-filter',
'active-filters', 'active-filters',
'checkout', 'checkout',
'cart', 'cart',