From 9db927de30514d0e28833390e53b3672d9bb7d49 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 12 Sep 2023 09:36:44 +0200 Subject: [PATCH] Product Gallery Thumbnails: Interactivity API directives (https://github.com/woocommerce/woocommerce-blocks/pull/10776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix "On sale" badge class for shop * Add class to sale badge * Move the thumbnails featching logic to an utils file. Add context directive with thumbnails data to the Product Gallery block. Add on-click directives to the Thumbnails block * Product Gallery Thumbnails: Remove the legacy thumbnail markup * Product Gallery Thumbnails: Add Large Image replacing * update the main image when the thumbnail is clicked * add E2E tests * fix typo * fix warning on the frontend * address feedback * update E2E test * improve comment * fix indentation * improve E2E test * improve flaky test * improve E2E test * improve comments * improve E2E test * try now * add comment * skip test * reset script * update todo comment --------- Co-authored-by: Alba Rincón Co-authored-by: Alba Rincón Co-authored-by: Luigi --- .../js/blocks/product-gallery/block.json | 3 +- .../js/blocks/product-gallery/frontend.tsx | 40 +++-- .../product-gallery-large-image/style.scss | 4 + .../product-gallery-thumbnails/style.scss | 6 +- .../src/BlockTypes/ProductGallery.php | 24 +-- .../BlockTypes/ProductGalleryLargeImage.php | 84 ++++++--- .../BlockTypes/ProductGalleryThumbnails.php | 36 ++-- .../src/Utils/ProductGalleryUtils.php | 69 ++++++++ ...rge-image.block_theme.side_effects.spec.ts | 12 +- ...t-gallery.block_theme.side_effects.spec.ts | 165 ++++++++++++++++++ 10 files changed, 369 insertions(+), 74 deletions(-) create mode 100644 plugins/woocommerce-blocks/src/Utils/ProductGalleryUtils.php create mode 100644 plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/block.json b/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/block.json index 14e3fadad4a..7c21f820b9c 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/block.json +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/block.json @@ -21,6 +21,7 @@ "pagerDisplayMode": "pagerDisplayMode", "hoverZoom": "hoverZoom" }, + "usesContext": [ "postId" ], "attributes": { "thumbnailsPosition": { "type": "string", @@ -55,5 +56,5 @@ "default": "insideTheImage" } }, - "viewScript": "wc-product-gallery-interactivity-frontend" + "viewScript": "wc-product-gallery-frontend" } diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/frontend.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/frontend.tsx index bdc73bc159c..021cc8310ca 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/frontend.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/frontend.tsx @@ -9,15 +9,20 @@ interface State { interface Context { woocommerce: { - productGallery: { numberOfThumbnails: number }; + selectedImage: string; + imageId: string; }; } interface Selectors { woocommerce: { - productGallery: { - numberOfPages: ( store: unknown ) => number; - }; + isSelected: ( store: unknown ) => boolean; + }; +} + +interface Actions { + woocommerce: { + handleClick: ( context: Context ) => void; }; } @@ -25,22 +30,27 @@ interface Store { state: State; context: Context; selectors: Selectors; - ref: HTMLElement; + actions: Actions; + ref?: HTMLElement; } -type SelectorsStore = Pick< Store, 'context' | 'selectors' >; - interactivityApiStore( { + state: {}, selectors: { woocommerce: { - productGallery: { - numberOfPages: ( store: SelectorsStore ) => { - const { context } = store; - - return context.woocommerce.productGallery - .numberOfThumbnails; - }, + isSelected: ( { context }: Store ) => { + return ( + context?.woocommerce.selectedImage === + context?.woocommerce.imageId + ); }, }, }, -} as Store ); + actions: { + woocommerce: { + handleClick: ( { context }: Store ) => { + context.woocommerce.selectedImage = context.woocommerce.imageId; + }, + }, + }, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/style.scss index a8b7c2a6080..597206fdd56 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/style.scss +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/style.scss @@ -13,6 +13,10 @@ } } + img[hidden] { + display: none; + } + .wc-block-product-gallery-large-image__inner-blocks { pointer-events: none; display: flex; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/style.scss index 615d866f68c..2dfe7612c13 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/style.scss +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/style.scss @@ -1,4 +1,8 @@ -.woocommerce { +.wp-block-woocommerce-product-gallery-thumbnails { + img { + cursor: pointer; + } + .is-vertical .wc-block-components-product-gallery-thumbnails { display: flex; flex-direction: row; diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductGallery.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductGallery.php index 7c82e42b5f4..fcbe2d56e2d 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/ProductGallery.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductGallery.php @@ -1,6 +1,8 @@ trim( sprintf( 'woocommerce %1$s', $classname ) ) ) ); $html = sprintf( @@ -39,13 +34,20 @@ class ProductGallery extends AbstractBlock { $wrapper_attributes, $content ); - $p = new \WP_HTML_Tag_Processor( $content ); + + $p = new \WP_HTML_Tag_Processor( $content ); if ( $p->next_tag() ) { $p->set_attribute( 'data-wc-interactive', true ); $p->set_attribute( 'data-wc-context', - wp_json_encode( array( 'woocommerce' => array( 'productGallery' => array( 'numberOfThumbnails' => 0 ) ) ) ) + wp_json_encode( + array( + 'woocommerce' => array( + 'selectedImage' => $product->get_image_id(), + ), + ) + ) ); $html = $p->get_updated_html(); } diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductGalleryLargeImage.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductGalleryLargeImage.php index 51ff063ba01..02f0dbbe02c 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/ProductGalleryLargeImage.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductGalleryLargeImage.php @@ -1,6 +1,8 @@ remove_class( 'wp-block-woocommerce-product-gallery-large-image' ); $content = $processor->get_updated_html(); - $image_html = wp_get_attachment_image( - $product->get_image_id(), - 'full', - false, - array( - 'class' => 'wc-block-woocommerce-product-gallery-large-image__image', - ) - ); + [ $visible_main_image, $main_images ] = $this->get_main_images_html( $block->context, $post_id ); - [$directives, $image_html] = $block->context['hoverZoom'] ? $this->get_html_with_interactivity( $image_html ) : array( array(), $image_html ); + $directives = $this->get_directives( $block->context ); return strtr( '', array( - '{image}' => $image_html, - '{content}' => $content, - '{directives}' => array_reduce( + '{visible_main_image}' => $visible_main_image, + '{main_images}' => implode( ' ', $main_images ), + '{content}' => $content, + '{directives}' => array_reduce( array_keys( $directives ), function( $carry, $key ) use ( $directives ) { return $carry . ' ' . $key . '="' . esc_attr( $directives[ $key ] ) . '"'; @@ -102,12 +99,54 @@ class ProductGalleryLargeImage extends AbstractBlock { } /** - * Get the HTML that adds interactivity to the image. This is used for the hover zoom effect. + * Get the main images html code. The first element of the array contains the HTML of the first image that is visible, the second element contains the HTML of the other images that are hidden. + * + * @param array $context The block context. + * @param int $product_id The product id. * - * @param string $image_html The image HTML. * @return array */ - private function get_html_with_interactivity( $image_html ) { + private function get_main_images_html( $context, $product_id ) { + $attributes = array( + 'data-wc-bind--hidden' => '!selectors.woocommerce.isSelected', + 'hidden' => true, + 'class' => 'wc-block-woocommerce-product-gallery-large-image__image', + + ); + + if ( $context['hoverZoom'] ) { + $attributes['class'] .= ' wc-block-woocommerce-product-gallery-large-image__image--hoverZoom'; + $attributes['data-wc-bind--style'] = 'selectors.woocommerce.styles'; + } + + $main_images = ProductGalleryUtils::get_product_gallery_images( + $product_id, + 'full', + $attributes + ); + + $visible_main_image = array_shift( $main_images ); + $visible_main_image_processor = new \WP_HTML_Tag_Processor( $visible_main_image ); + $visible_main_image_processor->next_tag(); + $visible_main_image_processor->remove_attribute( 'hidden' ); + $visible_main_image = $visible_main_image_processor->get_updated_html(); + + return array( $visible_main_image, $main_images ); + + } + + /** + * Get directives for the hover zoom. + * + * @param array $block_context The block context. + * + * @return array + */ + private function get_directives( $block_context ) { + if ( ! $block_context['hoverZoom'] ) { + return array(); + } + $context = array( 'woocommerce' => array( 'styles' => array( @@ -117,21 +156,10 @@ class ProductGalleryLargeImage extends AbstractBlock { ), ); - $directives = array( + return array( 'data-wc-on--mousemove' => 'actions.woocommerce.handleMouseMove', 'data-wc-on--mouseleave' => 'actions.woocommerce.handleMouseLeave', 'data-wc-context' => wp_json_encode( $context, JSON_NUMERIC_CHECK ), ); - - $image_html_processor = new \WP_HTML_Tag_Processor( $image_html ); - $image_html_processor->next_tag( 'img' ); - $image_html_processor->add_class( 'wc-block-woocommerce-product-gallery-large-image__image--hoverZoom' ); - $image_html_processor->set_attribute( 'data-wc-bind--style', 'selectors.woocommerce.styles' ); - $image_html = $image_html_processor->get_updated_html(); - - return array( - $directives, - $image_html, - ); } } diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductGalleryThumbnails.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductGalleryThumbnails.php index 7974f7462ec..4052268736b 100644 --- a/plugins/woocommerce-blocks/src/BlockTypes/ProductGalleryThumbnails.php +++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductGalleryThumbnails.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes; use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils; +use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils; /** * ProductGalleryLargeImage class. @@ -54,33 +55,38 @@ class ProductGalleryThumbnails extends AbstractBlock { $html = ''; if ( $product ) { - $attachment_ids = $product->get_gallery_image_ids(); - if ( $attachment_ids && $post_thumbnail_id ) { - $html .= wc_get_gallery_image_html( $post_thumbnail_id, true ); + $post_thumbnail_id = $product->get_image_id(); + $product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'thumbnail', array() ); + if ( $product_gallery_images && $post_thumbnail_id ) { $number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3; $thumbnails_count = 1; - foreach ( $attachment_ids as $attachment_id ) { - if ( $thumbnails_count >= $number_of_thumbnails ) { + foreach ( $product_gallery_images as $product_gallery_image_html ) { + if ( $thumbnails_count > $number_of_thumbnails ) { break; } - /** - * Filter the HTML markup for a single product image thumbnail in the gallery. - * - * @param string $thumbnail_html The HTML markup for the thumbnail. - * @param int $attachment_id The attachment ID of the thumbnail. - * - * @since 7.9.0 - */ - $html .= apply_filters( 'woocommerce_single_product_image_thumbnail_html', wc_get_gallery_image_html( $attachment_id ), $attachment_id ); // phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped + $html .= ''; $thumbnails_count++; } } return sprintf( - '