diff --git a/assets/js/atomic/blocks/index.js b/assets/js/atomic/blocks/index.js index 5a1ff791cbd..6745263834d 100644 --- a/assets/js/atomic/blocks/index.js +++ b/assets/js/atomic/blocks/index.js @@ -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'; diff --git a/assets/js/atomic/blocks/product-elements/product-image-gallery/block.json b/assets/js/atomic/blocks/product-elements/product-image-gallery/block.json new file mode 100644 index 00000000000..0a1247cbc34 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-image-gallery/block.json @@ -0,0 +1,17 @@ +{ + "name": "woocommerce/product-image-gallery", + "version": "1.0.0", + "title": "Product Image Gallery", + "icon": "gallery", + "description": "Display a product's 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" +} diff --git a/assets/js/atomic/blocks/product-elements/product-image-gallery/edit.tsx b/assets/js/atomic/blocks/product-elements/product-image-gallery/edit.tsx new file mode 100644 index 00000000000..df6fb8ed95e --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-image-gallery/edit.tsx @@ -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 ( +
+ Placeholder +
+ { [ ...Array( 4 ).keys() ].map( ( index ) => { + return ( + Placeholder + ); + } ) } +
+
+ ); +}; + +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 ( +
+ + + +
+ ); + } + // We have work on this case when we will work on the Single Product block. + return ''; +}; + +export default Edit; diff --git a/assets/js/atomic/blocks/product-elements/product-image-gallery/editor.scss b/assets/js/atomic/blocks/product-elements/product-image-gallery/editor.scss new file mode 100644 index 00000000000..40696afdbba --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-image-gallery/editor.scss @@ -0,0 +1,13 @@ +.wc-block-editor-product-gallery { + img { + width: 500px; + height: 500px; + } + .wc-block-editor-product-gallery__other-images { + img { + width: 100px; + height: 100px; + margin: 5px; + } + } +} diff --git a/assets/js/atomic/blocks/product-elements/product-image-gallery/index.ts b/assets/js/atomic/blocks/product-elements/product-image-gallery/index.ts new file mode 100644 index 00000000000..ca81e18eee9 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-image-gallery/index.ts @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import { gallery as icon } from '@wordpress/icons'; +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: () => { + // @ts-expect-error: `registerBlockType` is a function that is typed in WordPress core. + registerBlockType( metadata, { + icon, + edit, + } ); + }, + unregisterBlockFn: () => { + unregisterBlockType( metadata.name ); + }, + blockName: metadata.name, +} ); diff --git a/assets/js/atomic/utils/index.js b/assets/js/atomic/utils/index.js index f10a14b6858..06280bb433f 100644 --- a/assets/js/atomic/utils/index.js +++ b/assets/js/atomic/utils/index.js @@ -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'; diff --git a/assets/js/atomic/utils/register-block-single-product-template.ts b/assets/js/atomic/utils/register-block-single-product-template.ts new file mode 100644 index 00000000000..675e335b06d --- /dev/null +++ b/assets/js/atomic/utils/register-block-single-product-template.ts @@ -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( () => { + 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(); + } + }, 'core/edit-site' ); +}; diff --git a/assets/js/types/type-guards/object.ts b/assets/js/types/type-guards/object.ts index c90646df629..889ab7fc26a 100644 --- a/assets/js/types/type-guards/object.ts +++ b/assets/js/types/type-guards/object.ts @@ -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; +}; diff --git a/assets/js/types/type-guards/test/object.ts b/assets/js/types/type-guards/test/object.ts new file mode 100644 index 00000000000..5ce4e73846c --- /dev/null +++ b/assets/js/types/type-guards/test/object.ts @@ -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 ); + } ); + } ); +} ); diff --git a/images/block-placeholders/product-image-gallery.svg b/images/block-placeholders/product-image-gallery.svg new file mode 100644 index 00000000000..a9a75f82cf4 --- /dev/null +++ b/images/block-placeholders/product-image-gallery.svg @@ -0,0 +1,9 @@ + + + + Layer 1 + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 96d1f2aea73..06fdcaaf453 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,7 +82,7 @@ "@types/wordpress__blocks": "11.0.7", "@types/wordpress__components": "^23.0.0", "@types/wordpress__core-data": "^2.4.5", - "@types/wordpress__data": "^6.0.1", + "@types/wordpress__data": "^6.0.2", "@types/wordpress__data-controls": "2.2.0", "@types/wordpress__editor": "^11.0.0", "@types/wordpress__notices": "^3.5.0", @@ -11680,9 +11680,10 @@ "license": "MIT" }, "node_modules/@types/wordpress__data": { - "version": "6.0.1", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/wordpress__data/-/wordpress__data-6.0.2.tgz", + "integrity": "sha512-Pu67knXXoTWgCpxTKwePNZz/iKkYe8AQbkkSD/Ba1mw8t4zgEM+jJs5IV5N5ij/awwjs4Subj8mkvS3jMTDwyw==", "dev": true, - "license": "MIT", "dependencies": { "@types/react": "*", "redux": "^4.1.0" @@ -58730,7 +58731,9 @@ "dev": true }, "@types/wordpress__data": { - "version": "6.0.1", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/wordpress__data/-/wordpress__data-6.0.2.tgz", + "integrity": "sha512-Pu67knXXoTWgCpxTKwePNZz/iKkYe8AQbkkSD/Ba1mw8t4zgEM+jJs5IV5N5ij/awwjs4Subj8mkvS3jMTDwyw==", "dev": true, "requires": { "@types/react": "*", diff --git a/package.json b/package.json index 6b24618f34c..9474f765df1 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "@types/wordpress__blocks": "11.0.7", "@types/wordpress__components": "^23.0.0", "@types/wordpress__core-data": "^2.4.5", - "@types/wordpress__data": "^6.0.1", + "@types/wordpress__data": "^6.0.2", "@types/wordpress__data-controls": "2.2.0", "@types/wordpress__editor": "^11.0.0", "@types/wordpress__notices": "^3.5.0", diff --git a/src/BlockTypes/ProductImageGallery.php b/src/BlockTypes/ProductImageGallery.php new file mode 100644 index 00000000000..43e84ebbe32 --- /dev/null +++ b/src/BlockTypes/ProductImageGallery.php @@ -0,0 +1,60 @@ +context['postId']; + global $product; + $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( + '', + esc_attr( $classname ), + $product_image_gallery_html + ); + + } +} diff --git a/src/BlockTypesController.php b/src/BlockTypesController.php index ad4ea3f4ee6..e9355759ec0 100644 --- a/src/BlockTypesController.php +++ b/src/BlockTypesController.php @@ -187,6 +187,7 @@ protected function get_block_types() { 'ProductCategory', 'ProductCategoryList', 'ProductImage', + 'ProductImageGallery', 'ProductNew', 'ProductOnSale', 'ProductPrice', diff --git a/tsconfig.json b/tsconfig.json index 2d88e8033f9..2a1a5b6c03b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "./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"