This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Product Query: Add support for filtering by attributes within the blo…
…ck (#7743)
- Loading branch information
1 parent
76e615b
commit 0d3e954
Showing
6 changed files
with
275 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
156 changes: 156 additions & 0 deletions
156
assets/js/blocks/product-query/inspector-controls/attributes-filter.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { __ } from '@wordpress/i18n'; | ||
import { | ||
FormTokenField, | ||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis | ||
__experimentalToolsPanelItem as ToolsPanelItem, | ||
} from '@wordpress/components'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { | ||
AttributeMetadata, | ||
AttributeWithTerms, | ||
ProductQueryBlock, | ||
} from '../types'; | ||
import useProductAttributes from '../useProductAttributes'; | ||
import { setQueryAttribute } from '../utils'; | ||
|
||
function getAttributeMetadataFromToken( | ||
token: string, | ||
productsAttributes: AttributeWithTerms[] | ||
) { | ||
const [ attributeLabel, termName ] = token.split( ': ' ); | ||
const taxonomy = productsAttributes.find( | ||
( attribute ) => attribute.attribute_label === attributeLabel | ||
); | ||
|
||
if ( ! taxonomy ) | ||
throw new Error( 'Product Query Filter: Invalid attribute label' ); | ||
|
||
const term = taxonomy.terms.find( | ||
( currentTerm ) => currentTerm.name === termName | ||
); | ||
|
||
if ( ! term ) throw new Error( 'Product Query Filter: Invalid term name' ); | ||
|
||
return { | ||
taxonomy: `pa_${ taxonomy.attribute_name }`, | ||
termId: term.id, | ||
}; | ||
} | ||
|
||
function getAttributeFromMetadata( | ||
metadata: AttributeMetadata, | ||
productsAttributes: AttributeWithTerms[] | ||
) { | ||
const taxonomy = productsAttributes.find( | ||
( attribute ) => | ||
attribute.attribute_name === metadata.taxonomy.slice( 3 ) | ||
); | ||
|
||
return { | ||
taxonomy, | ||
term: taxonomy?.terms.find( ( term ) => term.id === metadata.termId ), | ||
}; | ||
} | ||
|
||
function getInputValueFromQueryParam( | ||
queryParam: AttributeMetadata[] | undefined, | ||
productAttributes: AttributeWithTerms[] | ||
): FormTokenField.Value[] { | ||
return ( | ||
queryParam?.map( ( metadata ) => { | ||
const { taxonomy, term } = getAttributeFromMetadata( | ||
metadata, | ||
productAttributes | ||
); | ||
|
||
return ! taxonomy || ! term | ||
? { | ||
title: __( | ||
'Saved taxonomy was perhaps deleted or the slug was changed.', | ||
'woo-gutenberg-products-block' | ||
), | ||
value: __( | ||
`Error with saved taxonomy`, | ||
'woo-gutenberg-products-block' | ||
), | ||
status: 'error', | ||
} | ||
: `${ taxonomy.attribute_label }: ${ term.name }`; | ||
} ) || [] | ||
); | ||
} | ||
|
||
export const AttributesFilter = ( props: ProductQueryBlock ) => { | ||
const { query } = props.attributes; | ||
const { isLoadingAttributes, productsAttributes } = | ||
useProductAttributes( true ); | ||
|
||
const attributesSuggestions = productsAttributes.reduce( ( acc, curr ) => { | ||
const namespacedTerms = curr.terms.map( | ||
( term ) => `${ curr.attribute_label }: ${ term.name }` | ||
); | ||
|
||
return [ ...acc, ...namespacedTerms ]; | ||
}, [] as string[] ); | ||
|
||
return ( | ||
<ToolsPanelItem | ||
label={ __( 'Product Attributes', 'woo-gutenberg-products-block' ) } | ||
hasValue={ () => query.__woocommerceAttributes?.length } | ||
> | ||
<FormTokenField | ||
disabled={ isLoadingAttributes } | ||
label={ __( | ||
'Product Attributes', | ||
'woo-gutenberg-products-block' | ||
) } | ||
onChange={ ( attributes ) => { | ||
let __woocommerceAttributes; | ||
|
||
try { | ||
__woocommerceAttributes = attributes.map( | ||
( attribute ) => { | ||
attribute = | ||
typeof attribute === 'string' | ||
? attribute | ||
: attribute.value; | ||
|
||
return getAttributeMetadataFromToken( | ||
attribute, | ||
productsAttributes | ||
); | ||
} | ||
); | ||
|
||
setQueryAttribute( props, { | ||
__woocommerceAttributes, | ||
} ); | ||
} catch ( ok ) { | ||
// Not required to do anything here | ||
// Input validation is handled by the `validateInput` | ||
// below, and we don't need to save anything. | ||
} | ||
} } | ||
suggestions={ attributesSuggestions } | ||
validateInput={ ( value: string ) => | ||
attributesSuggestions.includes( value ) | ||
} | ||
value={ | ||
isLoadingAttributes | ||
? [ __( 'Loading…', 'woo-gutenberg-products-block' ) ] | ||
: getInputValueFromQueryParam( | ||
query.__woocommerceAttributes, | ||
productsAttributes | ||
) | ||
} | ||
__experimentalExpandOnFocus={ true } | ||
/> | ||
</ToolsPanelItem> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { useEffect, useRef, useState } from '@wordpress/element'; | ||
import { getTerms } from '@woocommerce/editor-components/utils'; | ||
import { getSetting } from '@woocommerce/settings'; | ||
import { AttributeSetting } from '@woocommerce/types'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { AttributeWithTerms } from './types'; | ||
|
||
export default function useProductAttributes( shouldLoadAttributes: boolean ) { | ||
const STORE_ATTRIBUTES = getSetting< AttributeSetting[] >( | ||
'attributes', | ||
[] | ||
); | ||
const [ isLoadingAttributes, setIsLoadingAttributes ] = useState( false ); | ||
const [ productsAttributes, setProductsAttributes ] = useState< | ||
AttributeWithTerms[] | ||
>( [] ); | ||
const hasLoadedAttributes = useRef( false ); | ||
|
||
useEffect( () => { | ||
if ( | ||
! shouldLoadAttributes || | ||
isLoadingAttributes || | ||
hasLoadedAttributes.current | ||
) | ||
return; | ||
|
||
async function fetchTerms() { | ||
setIsLoadingAttributes( true ); | ||
|
||
for ( const attribute of STORE_ATTRIBUTES ) { | ||
const terms = await getTerms( | ||
Number( attribute.attribute_id ) | ||
); | ||
|
||
setProductsAttributes( ( oldAttributes ) => [ | ||
...oldAttributes, | ||
{ | ||
...attribute, | ||
terms, | ||
}, | ||
] ); | ||
} | ||
|
||
hasLoadedAttributes.current = true; | ||
setIsLoadingAttributes( false ); | ||
} | ||
|
||
fetchTerms(); | ||
|
||
return () => { | ||
hasLoadedAttributes.current = true; | ||
}; | ||
}, [ STORE_ATTRIBUTES, isLoadingAttributes, shouldLoadAttributes ] ); | ||
|
||
return { isLoadingAttributes, productsAttributes }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters