Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Add Product Image Gallery #8235

Merged
merged 20 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
38fcacb
Add Product Image Gallery #8233
gigitux Jan 18, 2023
2a1b52c
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce-bl…
gigitux Jan 20, 2023
5f889e6
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce-bl…
gigitux Jan 30, 2023
13dc046
Add Product Image Gallery block
gigitux Jan 30, 2023
1dc7baa
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce-bl…
gigitux Jan 30, 2023
21a1fea
remove support global styles
gigitux Jan 30, 2023
d9900e0
remove support global styles
gigitux Jan 30, 2023
75d00bb
Merge branch 'add/8233-product-image-gallery-block' of https://github…
gigitux Jan 30, 2023
f8e00c9
Merge branch 'trunk' into add/8233-product-image-gallery-block
gigitux Jan 30, 2023
795a698
address CSS feedback
gigitux Jan 31, 2023
90c71d3
Merge branch 'add/8233-product-image-gallery-block' of https://github…
gigitux Jan 31, 2023
dc48ddf
add support for the custom classname
gigitux Jan 31, 2023
e526eff
remove save function
gigitux Jan 31, 2023
66595f1
add second parameter to the subscribe function
gigitux Feb 1, 2023
c701d7f
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce-bl…
gigitux Feb 3, 2023
5a4ac3b
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce-bl…
gigitux Feb 13, 2023
8aee841
update @types/wordpress__data package
gigitux Feb 13, 2023
32f97dd
update placeholder, icon and description
gigitux Feb 13, 2023
e3be328
update tsconfig
gigitux Feb 13, 2023
b6a8eb5
Merge branch 'trunk' into add/8233-product-image-gallery-block
gigitux Feb 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/js/atomic/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ import './product-elements/category-list';
import './product-elements/tag-list';
import './product-elements/stock-indicator';
import './product-elements/add-to-cart';
import './product-elements/product-image-gallery';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "woocommerce/product-image-gallery",
"version": "1.0.0",
"title": "Product Image Gallery",
"icon": "image",
"description": "Display the main product images.",
"category": "woocommerce",
"supports": {
"align": true,
"reusable": false
},
"keywords": [ "WooCommerce" ],
"usesContext": [ "postId", "postType", "queryId" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2,
"$schema": "https://schemas.wp.org/trunk/block.json"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* External dependencies
*/
import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
import { isEmptyObject } from '@woocommerce/types';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockAttributes } from '@wordpress/blocks';
import { Disabled } from '@wordpress/components';

/**
* Internal dependencies
*/
import './editor.scss';

const Placeholder = () => {
return (
<div className="wc-block-editor-product-gallery">
<img
src={ `${ WC_BLOCKS_IMAGE_URL }template-placeholders/fallback.svg` }
alt="Placeholder"
/>
<div className="wc-block-editor-product-gallery__other-images">
{ [ ...Array( 4 ).keys() ].map( ( index ) => {
return (
<img
key={ index }
src={ `${ WC_BLOCKS_IMAGE_URL }template-placeholders/fallback.svg` }
alt="Placeholder"
/>
);
} ) }
</div>
</div>
);
};

type Context = {
postId: string;
postType: string;
queryId: string;
};

interface Props {
attributes: BlockAttributes;
context: Context;
}

const Edit = ( { context }: Props ) => {
const blockProps = useBlockProps();

if ( isEmptyObject( context ) ) {
return (
<div { ...blockProps }>
<Disabled>
<Placeholder />
</Disabled>
</div>
);
}
// We have work on this case when we will work on the Single Product block.
return '';
};

export default Edit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.wc-block-editor-product-gallery {
img {
width: 500px;
height: 500px;
}
.wc-block-editor-product-gallery__other-images {
img {
width: 100px;
height: 100px;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* External dependencies
*/
import { registerBlockType, unregisterBlockType } from '@wordpress/blocks';
import { registerBlockSingleProductTemplate } from '@woocommerce/atomic-utils';

/**
* Internal dependencies
*/
import edit from './edit';
import metadata from './block.json';

registerBlockSingleProductTemplate( {
registerBlockFn: () => {
registerBlockType( metadata, {
edit,
} );
},
unregisterBlockFn: () => {
unregisterBlockType( metadata.name );
},
blockName: metadata.name,
} );
1 change: 1 addition & 0 deletions assets/js/atomic/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './get-block-map';
export * from './create-blocks-from-template';
export * from './render-parent-block';
export * from './render-standalone-blocks';
export * from './register-block-single-product-template';
46 changes: 46 additions & 0 deletions assets/js/atomic/utils/register-block-single-product-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* External dependencies
*/
import { getBlockType } from '@wordpress/blocks';
import { subscribe, select } from '@wordpress/data';

export const registerBlockSingleProductTemplate = ( {
registerBlockFn,
unregisterBlockFn,
blockName,
}: {
registerBlockFn: () => void;
unregisterBlockFn: () => void;
blockName: string;
} ) => {
let currentTemplateId: string | undefined;

subscribe( () => {
imanish003 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use debounce here to make this trigger less frequent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, I would avoid a debounce here to avoid any kind of race condition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious what can be the race condition and its drawback. The motivation behind my suggestion is to keep the editor performant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, for example, the user is editing another template, but the function isn't called due to the debounce and the block is still visible in the inserter.

Copy link
Member

@dinhtungdu dinhtungdu Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but the function isn't called due to the debounce

I don't think we will have this issue, as we just want to debounce them (by 100ms, for example), the subscription still triggers, but less frequently. So when the user switches to another template, the subscription may not trigger right away, but still fast enough to register/unregister the block before user interaction.

We don't have to do it now btw, we can go with the current PR and come back to this later when we see it affect the editor performance.

const previousTemplateId = currentTemplateId;
const store = select( 'core/edit-site' );
currentTemplateId = store?.getEditedPostId() as string | undefined;

if ( previousTemplateId === currentTemplateId ) {
return;
}

const parsedTemplate = currentTemplateId?.split( '//' )[ 1 ];

if ( parsedTemplate === null || parsedTemplate === undefined ) {
return;
}

const block = getBlockType( blockName );

if (
block === undefined &&
parsedTemplate.includes( 'single-product' )
) {
registerBlockFn();
}

if ( block !== undefined ) {
unregisterBlockFn();
}
} );
};
6 changes: 6 additions & 0 deletions assets/js/types/type-guards/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ export function objectHasProp< P extends PropertyKey >(
// The `in` operator throws a `TypeError` for non-object values.
return isObject( target ) && property in target;
}

export const isEmptyObject = < T extends { [ key: string ]: unknown } >(
object: T
) => {
return Object.keys( object ).length === 0;
};
25 changes: 25 additions & 0 deletions assets/js/types/type-guards/test/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* External dependencies
*/
import { isEmptyObject, isObject } from '@woocommerce/types';

describe( 'Object type-guards', () => {
describe( 'Testing isObject()', () => {
it( 'Correctly identifies an object', () => {
expect( isObject( {} ) ).toBe( true );
expect( isObject( { test: 'object' } ) ).toBe( true );
} );
it( 'Correctly rejects object-like things', () => {
expect( isObject( [] ) ).toBe( false );
expect( isObject( null ) ).toBe( false );
} );
} );
describe( 'Testing isEmptyObject()', () => {
it( 'Correctly identifies an empty object', () => {
expect( isEmptyObject( {} ) ).toBe( true );
} );
it( 'Correctly identifies an not empty object', () => {
expect( isEmptyObject( { name: 'Woo' } ) ).toBe( false );
} );
} );
} );
60 changes: 60 additions & 0 deletions src/BlockTypes/ProductImageGallery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;


/**
* ProductImageGallery class.
*/
class ProductImageGallery extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-image-gallery';

/**
* Register the context
*
* @var string
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}


/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {

$post_id = $block->context['postId'];
global $product;
imanish003 marked this conversation as resolved.
Show resolved Hide resolved
$product = wc_get_product( $post_id );

if ( class_exists( 'WC_Frontend_Scripts' ) ) {
$frontend_scripts = new \WC_Frontend_Scripts();
$frontend_scripts::load_scripts();
}

$classname = $attributes['className'] ?? '';

ob_start();
woocommerce_show_product_images();
$product_image_gallery_html = ob_get_clean();

return sprintf(
'<div class="wp-block-woocommerce-product-image-gallery %1$s">%2$s</div>',
esc_attr( $classname ),
$product_image_gallery_html
);

}
}
1 change: 1 addition & 0 deletions src/BlockTypesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ protected function get_block_types() {
'ProductCategory',
'ProductCategoryList',
'ProductImage',
'ProductImageGallery',
'ProductNew',
'ProductOnSale',
'ProductPrice',
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"include": [
"./assets/js/**/*",
"./packages/checkout/**/*",
"./assets/js/blocks/**/block.json",
"./assets/js/atomic/blocks/**/block.json",
"./assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/**/block.json",
"./storybook/**/*",
"./tests/js/setup-after-env.ts"
Expand Down