File "product-list.tsx"
Full Path: /home/warrior1/public_html/languages/wp-content/plugins/woocommerce/packages/woocommerce-blocks/assets/js/base/components/product-list/product-list.tsx
File size: 6.83 KB
MIME-type: text/x-java
Charset: utf-8
/**
* External dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';
import { isEqual } from 'lodash';
import classnames from 'classnames';
import Pagination from '@woocommerce/base-components/pagination';
import { useEffect } from '@wordpress/element';
import { usePrevious } from '@woocommerce/base-hooks';
import {
useStoreEvents,
useStoreProducts,
useSynchronizedQueryState,
useQueryStateByKey,
} from '@woocommerce/base-context/hooks';
import withScrollToTop from '@woocommerce/base-hocs/with-scroll-to-top';
import { useInnerBlockLayoutContext } from '@woocommerce/shared-context';
import { speak } from '@wordpress/a11y';
/**
* Internal dependencies
*/
import NoProducts from './no-products';
import NoMatchingProducts from './no-matching-products';
import ProductSortSelect from './product-sort-select';
import ProductListItem from './product-list-item';
import {
GenerateQuery,
GetSortArgs,
Query,
AreQueryTotalsDifferent,
ProductListProps,
TotalQuery,
} from './types';
import './style.scss';
const getSortArgs: GetSortArgs = ( orderName ) => {
switch ( orderName ) {
case 'menu_order':
case 'popularity':
case 'rating':
case 'price':
return {
orderby: orderName,
order: 'asc',
};
case 'price-desc':
return {
orderby: 'price',
order: 'desc',
};
case 'date':
return {
orderby: 'date',
order: 'desc',
};
}
};
const generateQuery: GenerateQuery = ( {
sortValue,
currentPage,
attributes,
} ) => {
const { columns, rows } = attributes;
return {
...getSortArgs( sortValue ),
catalog_visibility: 'catalog',
per_page: columns * rows,
page: currentPage,
};
};
/**
* Given a query state, returns the same query without the attributes related to
* pagination and sorting.
*
* @param {Object} query Query to extract the attributes from.
*
* @return {Object} Same query without pagination and sorting attributes.
*/
const extractPaginationAndSortAttributes = ( query: Query ): TotalQuery => {
/* eslint-disable-next-line no-unused-vars */
const { order, orderby, page, per_page: perPage, ...totalQuery } = query;
return totalQuery || {};
};
const announceLoadingCompletion = ( totalProducts: number ): void => {
if ( ! Number.isFinite( totalProducts ) ) {
return;
}
if ( totalProducts === 0 ) {
speak( __( 'No products found', 'woo-gutenberg-products-block' ) );
} else {
speak(
sprintf(
/* translators: %s is an integer higher than 0 (1, 2, 3...) */
_n(
'%d product found',
'%d products found',
totalProducts,
'woo-gutenberg-products-block'
),
totalProducts
)
);
}
};
const areQueryTotalsDifferent: AreQueryTotalsDifferent = (
{ totalQuery: nextQuery, totalProducts: nextProducts },
{ totalQuery: currentQuery } = {}
) => ! isEqual( nextQuery, currentQuery ) && Number.isFinite( nextProducts );
const ProductList = ( {
attributes,
currentPage,
onPageChange,
onSortChange,
sortValue,
scrollToTop,
}: ProductListProps ): JSX.Element => {
// 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(
generateQuery( {
attributes,
sortValue,
currentPage,
} )
);
const { products, totalProducts, productsLoading } =
useStoreProducts( queryState );
const { parentClassName, parentName } = useInnerBlockLayoutContext();
const totalQuery = extractPaginationAndSortAttributes( queryState );
const { dispatchStoreEvent } = useStoreEvents();
// Only update previous query totals if the query is different and the total number of products is a finite number.
const previousQueryTotals = usePrevious(
{ totalQuery, totalProducts },
areQueryTotalsDifferent
);
// If the product list changes, trigger an event.
useEffect( () => {
dispatchStoreEvent( 'product-list-render', {
products,
listName: parentName,
} );
}, [ products, parentName, dispatchStoreEvent ] );
// If query state (excluding pagination/sorting attributes) changed, reset pagination to the first page.
useEffect( () => {
if ( isEqual( totalQuery, previousQueryTotals?.totalQuery ) ) {
return;
}
onPageChange( 1 );
// Make sure there was a previous query, so we don't announce it on page load.
if ( previousQueryTotals?.totalQuery ) {
announceLoadingCompletion( totalProducts );
}
}, [
previousQueryTotals?.totalQuery,
totalProducts,
onPageChange,
totalQuery,
] );
const onPaginationChange = ( newPage: number ) => {
scrollToTop( { focusableSelector: 'a, button' } );
onPageChange( newPage );
};
const getClassnames = () => {
const { columns, rows, alignButtons, align } = attributes;
const alignClass = typeof align !== 'undefined' ? 'align' + align : '';
return classnames(
parentClassName,
alignClass,
'has-' + columns + '-columns',
{
'has-multiple-rows': rows > 1,
'has-aligned-buttons': alignButtons,
}
);
};
const { contentVisibility } = attributes;
const perPage = attributes.columns * attributes.rows;
const totalPages =
! Number.isFinite( totalProducts ) &&
Number.isFinite( previousQueryTotals?.totalProducts ) &&
isEqual( totalQuery, previousQueryTotals?.totalQuery )
? Math.ceil( ( previousQueryTotals?.totalProducts || 0 ) / perPage )
: Math.ceil( totalProducts / perPage );
const listProducts = products.length
? products
: Array.from( { length: perPage } );
const hasProducts = products.length !== 0 || productsLoading;
const hasFilters =
productAttributes.length > 0 ||
productStockStatus.length > 0 ||
Number.isFinite( minPrice ) ||
Number.isFinite( maxPrice );
return (
<div className={ getClassnames() }>
{ contentVisibility?.orderBy && hasProducts && (
<ProductSortSelect
onChange={ onSortChange }
value={ sortValue }
/>
) }
{ ! hasProducts && hasFilters && (
<NoMatchingProducts
resetCallback={ () => {
setProductAttributes( [] );
setProductStockStatus( [] );
setMinPrice( null );
setMaxPrice( null );
} }
/>
) }
{ ! hasProducts && ! hasFilters && <NoProducts /> }
{ hasProducts && (
<ul
className={ classnames( `${ parentClassName }__products`, {
'is-loading-products': productsLoading,
} ) }
>
{ listProducts.map( ( product = {}, i: number ) => (
<ProductListItem
key={ product.id || i }
attributes={ attributes }
product={ product }
/>
) ) }
</ul>
) }
{ totalPages > 1 && (
<Pagination
currentPage={ currentPage }
onPageChange={ onPaginationChange }
totalPages={ totalPages }
/>
) }
</div>
);
};
export default withScrollToTop( ProductList );