Merge pull request woocommerce/woocommerce-blocks#57 from woocommerce/improve/specific

Specific Select improvements
This commit is contained in:
Claudiu Lodromanean 2018-03-12 10:04:44 -07:00 committed by GitHub
commit 4c3470ce18
3 changed files with 305 additions and 85 deletions

View File

@ -99,7 +99,8 @@ var _wp$components = wp.components,
withAPIData = _wp$components.withAPIData,
Dropdown = _wp$components.Dropdown,
Dashicon = _wp$components.Dashicon,
RangeControl = _wp$components.RangeControl;
RangeControl = _wp$components.RangeControl,
Tooltip = _wp$components.Tooltip;
var ToggleControl = InspectorControls.ToggleControl,
SelectControl = InspectorControls.SelectControl;
@ -452,6 +453,30 @@ var ProductsBlockSettingsEditor = function (_React$Component3) {
);
}
var done_button = wp.element.createElement(
'button',
{ type: 'button', className: 'button wc-products-settings__footer-button', onClick: this.props.done_callback },
__('Done')
);
if (['', 'specific', 'category', 'attribute'].includes(this.state.display) && !this.props.selected_display_setting.length) {
var done_tooltips = {
'': __('Please select which products you\'d like to display'),
specific: __('Please search for and select products to display'),
category: __('Please select at least one category to display'),
attribute: __('Please select an attribute')
};
done_button = wp.element.createElement(
Tooltip,
{ text: done_tooltips[this.state.display] },
wp.element.createElement(
'button',
{ type: 'button', className: 'button wc-products-settings__footer-button disabled' },
__('Done')
)
);
}
return wp.element.createElement(
'div',
{ className: 'wc-products-settings ' + (this.state.expanded_group ? 'expanded-group-' + this.state.expanded_group : '') },
@ -468,11 +493,7 @@ var ProductsBlockSettingsEditor = function (_React$Component3) {
wp.element.createElement(
'div',
{ className: 'wc-products-settings__footer' },
wp.element.createElement(
'button',
{ type: 'button', className: 'button wc-products-settings__footer-button', onClick: this.props.done_callback },
__('Done')
)
done_button
)
);
}
@ -798,12 +819,20 @@ registerBlockType('woocommerce/products', {
* @return Component
*/
function getSettingsEditor() {
var update_display_callback = function update_display_callback(value) {
if (display !== value) {
setAttributes({
display: value,
display_setting: []
});
}
};
return wp.element.createElement(ProductsBlockSettingsEditor, {
selected_display: display,
selected_display_setting: display_setting,
update_display_callback: function update_display_callback(value) {
return setAttributes({ display: value });
},
update_display_callback: update_display_callback,
update_display_setting_callback: function update_display_setting_callback(value) {
return setAttributes({ display_setting: value });
},
@ -843,7 +872,7 @@ registerBlockType('woocommerce/products', {
}
if ('specific' === display) {
shortcode_atts.set('include', display_setting.join(','));
shortcode_atts.set('ids', display_setting.join(','));
} else if ('category' === display) {
shortcode_atts.set('category', display_setting.join(','));
} else if ('featured' === display) {
@ -935,6 +964,13 @@ var _ReactTransitionGroup = ReactTransitionGroup,
TransitionGroup = _ReactTransitionGroup.TransitionGroup,
CSSTransition = _ReactTransitionGroup.CSSTransition;
/**
* Product data cache.
* Reduces the number of API calls and makes UI smoother and faster.
*/
var PRODUCT_DATA = {};
/**
* When the display mode is 'Specific products' search for and add products to the block.
*
@ -1046,7 +1082,7 @@ var ProductsSpecificSelect = exports.ProductsSpecificSelect = function (_React$C
selectedProducts: this.state.selectedProducts
}),
wp.element.createElement(ProductSpecificSelectedProducts, {
products: this.state.selectedProducts,
productIds: this.state.selectedProducts,
removeProductCallback: this.removeProduct.bind(this)
})
);
@ -1200,6 +1236,32 @@ var ProductSpecificSearchResults = withAPIData(function (props) {
return __('No products found');
}
// Populate the cache.
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = products.data[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var product = _step2.value;
PRODUCT_DATA[product.id] = product;
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return wp.element.createElement(ProductSpecificSearchResultsDropdown, {
products: products.data,
addProductCallback: addProductCallback,
@ -1236,13 +1298,13 @@ var ProductSpecificSearchResultsDropdown = function (_React$Component3) {
var productElements = [];
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator2 = products[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var product = _step2.value;
for (var _iterator3 = products[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var product = _step3.value;
if (selectedProducts.includes(product.id)) {
continue;
@ -1262,16 +1324,16 @@ var ProductSpecificSearchResultsDropdown = function (_React$Component3) {
));
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
@ -1367,27 +1429,146 @@ var ProductSpecificSearchResultsDropdownElement = function (_React$Component4) {
var ProductSpecificSelectedProducts = withAPIData(function (props) {
if (!props.products.length) {
if (!props.productIds.length) {
return {
products: []
};
}
return {
products: '/wc/v2/products?include=' + props.products.join(',')
};
})(function (_ref2) {
var products = _ref2.products,
removeProductCallback = _ref2.removeProductCallback;
// Determine which products are not already in the cache and only fetch uncached products.
var uncachedProducts = [];
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
if (!products.data) {
return null;
try {
for (var _iterator4 = props.productIds[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var productId = _step4.value;
if (!PRODUCT_DATA.hasOwnProperty(productId)) {
uncachedProducts.push(productId);
}
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
if (0 === products.data.length) {
return {
products: uncachedProducts.length ? '/wc/v2/products?include=' + uncachedProducts.join(',') : []
};
})(function (_ref2) {
var productIds = _ref2.productIds,
products = _ref2.products,
removeProductCallback = _ref2.removeProductCallback;
// Add new products to cache.
if (products.data) {
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator5 = products.data[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var product = _step5.value;
PRODUCT_DATA[product.id] = product;
}
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
}
if (0 === productIds.length) {
return __('No products selected');
}
var productElements = [];
var _loop = function _loop(productId) {
// Skip products that aren't in the cache yet or failed to fetch.
if (!PRODUCT_DATA.hasOwnProperty(productId)) {
return 'continue';
}
var productData = PRODUCT_DATA[productId];
productElements.push(wp.element.createElement(
'li',
{ className: 'wc-products-list-card__item' },
wp.element.createElement(
'div',
{ className: 'wc-products-list-card__content' },
wp.element.createElement('img', { src: productData.images[0].src }),
wp.element.createElement(
'span',
{ className: 'wc-products-list-card__content-item-name' },
productData.name
),
wp.element.createElement(
'button',
{
type: 'button',
id: 'product-' + productData.id,
onClick: function onClick() {
removeProductCallback(productData.id);
} },
wp.element.createElement(Dashicon, { icon: 'no-alt' })
)
)
));
};
var _iteratorNormalCompletion6 = true;
var _didIteratorError6 = false;
var _iteratorError6 = undefined;
try {
for (var _iterator6 = productIds[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
var productId = _step6.value;
var _ret = _loop(productId);
if (_ret === 'continue') continue;
}
} catch (err) {
_didIteratorError6 = true;
_iteratorError6 = err;
} finally {
try {
if (!_iteratorNormalCompletion6 && _iterator6.return) {
_iterator6.return();
}
} finally {
if (_didIteratorError6) {
throw _iteratorError6;
}
}
}
return wp.element.createElement(
'div',
{ className: 'wc-products-list-card__results-wrapper' },
@ -1397,32 +1578,7 @@ var ProductSpecificSelectedProducts = withAPIData(function (props) {
wp.element.createElement(
'ul',
null,
products.data.map(function (product) {
return wp.element.createElement(
'li',
{ className: 'wc-products-list-card__item' },
wp.element.createElement(
'div',
{ className: 'wc-products-list-card__content' },
wp.element.createElement('img', { src: product.images[0].src }),
wp.element.createElement(
'span',
{ className: 'wc-products-list-card__content-item-name' },
product.name
),
wp.element.createElement(
'button',
{
type: 'button',
id: 'product-' + product.id,
onClick: function onClick() {
removeProductCallback(product.id);
} },
wp.element.createElement(Dashicon, { icon: 'no-alt' })
)
)
);
})
productElements
)
)
);

View File

@ -1,7 +1,7 @@
const { __ } = wp.i18n;
const { RawHTML } = wp.element;
const { registerBlockType, InspectorControls, BlockControls } = wp.blocks;
const { Toolbar, withAPIData, Dropdown, Dashicon, RangeControl } = wp.components;
const { Toolbar, withAPIData, Dropdown, Dashicon, RangeControl, Tooltip } = wp.components;
const { ToggleControl, SelectControl } = InspectorControls;
import { ProductsSpecificSelect } from './views/specific-select.jsx';
@ -267,6 +267,23 @@ class ProductsBlockSettingsEditor extends React.Component {
);
}
let done_button = <button type="button" className="button wc-products-settings__footer-button" onClick={ this.props.done_callback }>{ __( 'Done' ) }</button>;
if ( ['', 'specific', 'category', 'attribute'].includes( this.state.display ) && ! this.props.selected_display_setting.length ) {
const done_tooltips = {
'': __( 'Please select which products you\'d like to display' ),
specific: __( 'Please search for and select products to display' ),
category: __( 'Please select at least one category to display' ),
attribute: __( 'Please select an attribute' ),
}
done_button = (
<Tooltip text={ done_tooltips[ this.state.display ] } >
<button type="button" className="button wc-products-settings__footer-button disabled">{ __( 'Done' ) }</button>
</Tooltip>
);
}
return (
<div className={ 'wc-products-settings ' + ( this.state.expanded_group ? 'expanded-group-' + this.state.expanded_group : '' ) }>
<h4 className="wc-products-settings__title"><Dashicon icon={ 'universal-access-alt' } /> { __( 'Products' ) }</h4>
@ -278,7 +295,7 @@ class ProductsBlockSettingsEditor extends React.Component {
{ extra_settings }
<div className="wc-products-settings__footer">
<button type="button" className="button wc-products-settings__footer-button" onClick={ this.props.done_callback }>{ __( 'Done' ) }</button>
{ done_button }
</div>
</div>
);
@ -526,11 +543,21 @@ registerBlockType( 'woocommerce/products', {
* @return Component
*/
function getSettingsEditor() {
const update_display_callback = ( value ) => {
if ( display !== value ) {
setAttributes( {
display: value,
display_setting: [],
} );
}
};
return (
<ProductsBlockSettingsEditor
selected_display={ display }
selected_display_setting={ display_setting }
update_display_callback={ ( value ) => setAttributes( { display: value } ) }
update_display_callback={ update_display_callback }
update_display_setting_callback={ ( value ) => setAttributes( { display_setting: value } ) }
done_callback={ () => setAttributes( { edit_mode: false } ) }
/>
@ -564,7 +591,7 @@ registerBlockType( 'woocommerce/products', {
}
if ( 'specific' === display ) {
shortcode_atts.set( 'include', display_setting.join( ',' ) );
shortcode_atts.set( 'ids', display_setting.join( ',' ) );
} else if ( 'category' === display ) {
shortcode_atts.set( 'category', display_setting.join( ',' ) );
} else if ( 'featured' === display ) {

View File

@ -2,6 +2,12 @@ const { __ } = wp.i18n;
const { Toolbar, withAPIData, Dropdown, Dashicon } = wp.components;
const { TransitionGroup, CSSTransition } = ReactTransitionGroup;
/**
* Product data cache.
* Reduces the number of API calls and makes UI smoother and faster.
*/
const PRODUCT_DATA = {};
/**
* When the display mode is 'Specific products' search for and add products to the block.
*
@ -76,7 +82,7 @@ export class ProductsSpecificSelect extends React.Component {
selectedProducts={ this.state.selectedProducts }
/>
<ProductSpecificSelectedProducts
products={ this.state.selectedProducts }
productIds={ this.state.selectedProducts }
removeProductCallback={ this.removeProduct.bind( this ) }
/>
</div>
@ -194,6 +200,11 @@ const ProductSpecificSearchResults = withAPIData( ( props ) => {
return __( 'No products found' );
}
// Populate the cache.
for ( let product of products.data ) {
PRODUCT_DATA[ product.id ] = product;
}
return <ProductSpecificSearchResultsDropdown
products={ products.data }
addProductCallback={ addProductCallback }
@ -297,42 +308,68 @@ class ProductSpecificSearchResultsDropdownElement extends React.Component {
*/
const ProductSpecificSelectedProducts = withAPIData( ( props ) => {
if ( ! props.products.length ) {
if ( ! props.productIds.length ) {
return {
products: []
};
}
return {
products: '/wc/v2/products?include=' + props.products.join( ',' )
};
} )( ( { products, removeProductCallback } ) => {
if ( ! products.data ) {
return null;
// Determine which products are not already in the cache and only fetch uncached products.
let uncachedProducts = [];
for( const productId of props.productIds ) {
if ( ! PRODUCT_DATA.hasOwnProperty( productId ) ) {
uncachedProducts.push( productId );
}
}
if ( 0 === products.data.length ) {
return {
products: uncachedProducts.length ? '/wc/v2/products?include=' + uncachedProducts.join( ',' ) : []
};
} )( ( { productIds, products, removeProductCallback } ) => {
// Add new products to cache.
if ( products.data ) {
for ( const product of products.data ) {
PRODUCT_DATA[ product.id ] = product;
}
}
if ( 0 === productIds.length ) {
return __( 'No products selected' );
}
const productElements = [];
for ( const productId of productIds ) {
// Skip products that aren't in the cache yet or failed to fetch.
if ( ! PRODUCT_DATA.hasOwnProperty( productId ) ) {
continue;
}
const productData = PRODUCT_DATA[ productId ];
productElements.push(
<li className="wc-products-list-card__item">
<div className="wc-products-list-card__content">
<img src={ productData.images[0].src } />
<span className="wc-products-list-card__content-item-name">{ productData.name }</span>
<button
type="button"
id={ 'product-' + productData.id }
onClick={ function() { removeProductCallback( productData.id ) } } >
<Dashicon icon={ 'no-alt' } />
</button>
</div>
</li>
);
}
return (
<div className="wc-products-list-card__results-wrapper">
<div role="menu" className="wc-products-list-card__results" aria-orientation="vertical" aria-label="{ __( 'Products list' ) }">
<ul>
{ products.data.map( ( product ) => (
<li className="wc-products-list-card__item">
<div className="wc-products-list-card__content">
<img src={ product.images[0].src } />
<span className="wc-products-list-card__content-item-name">{ product.name }</span>
<button
type="button"
id={ 'product-' + product.id }
onClick={ function() { removeProductCallback( product.id ) } } >
<Dashicon icon={ 'no-alt' } />
</button>
</div>
</li>
) ) }
{ productElements }
</ul>
</div>
</div>