From 90d9e2faa805a100b4b1116bc68dac2e2d705454 Mon Sep 17 00:00:00 2001 From: Sam Seay Date: Fri, 24 Nov 2023 12:33:28 +0800 Subject: [PATCH 01/11] Translate the prefixes passed to post-terms in product-meta. (#11811) --- .../atomic/blocks/product-elements/product-meta/edit.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/assets/js/atomic/blocks/product-elements/product-meta/edit.tsx b/assets/js/atomic/blocks/product-elements/product-meta/edit.tsx index a189cea2c3e..f6ab5077482 100644 --- a/assets/js/atomic/blocks/product-elements/product-meta/edit.tsx +++ b/assets/js/atomic/blocks/product-elements/product-meta/edit.tsx @@ -3,6 +3,7 @@ */ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; import { InnerBlockTemplate } from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -28,14 +29,17 @@ const Edit = () => { [ 'core/post-terms', { - prefix: 'Category: ', + prefix: __( + 'Category: ', + 'woo-gutenberg-products-block' + ), term: 'product_cat', }, ], [ 'core/post-terms', { - prefix: 'Tags: ', + prefix: __( 'Tags: ', 'woo-gutenberg-products-block' ), term: 'product_tag', }, ], From 434c0e3f453f35042b1df711e6fc139addb76124 Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Fri, 24 Nov 2023 08:36:30 +0100 Subject: [PATCH 02/11] Remove isExperimental flag from product-query module which is not experimental (#10531) --- bin/webpack-entries.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bin/webpack-entries.js b/bin/webpack-entries.js index 4e04dccb1bf..75ca5defe87 100644 --- a/bin/webpack-entries.js +++ b/bin/webpack-entries.js @@ -75,9 +75,7 @@ const blocks = { }, 'product-new': {}, 'product-on-sale': {}, - 'product-query': { - isExperimental: true, - }, + 'product-query': {}, 'product-results-count': {}, 'product-search': {}, 'product-tag': {}, From 9d234fc6045d9618f513e1157332f45dc044d083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alba=20Rinc=C3=B3n?= Date: Fri, 24 Nov 2023 14:40:51 +0100 Subject: [PATCH 03/11] [Store Customization] AI-generated store name (#11878) * Add new ai/store-title endpoint to update the store title with an AI generated one * Add StoreTitle schema * Fix error to response param * Fix var name and tweak prompt * Update comment * Replace it by Ai generated if it's the default title * Return error if AI failed * Return false if the title is not updated with an AI one --- .../Routes/V1/AI/BusinessDescription.php | 1 - src/StoreApi/Routes/V1/AI/StoreTitle.php | 146 ++++++++++++++++++ src/StoreApi/RoutesController.php | 9 +- src/StoreApi/SchemaController.php | 1 + .../Schemas/V1/AI/StoreTitleSchema.php | 47 ++++++ 5 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 src/StoreApi/Routes/V1/AI/StoreTitle.php create mode 100644 src/StoreApi/Schemas/V1/AI/StoreTitleSchema.php diff --git a/src/StoreApi/Routes/V1/AI/BusinessDescription.php b/src/StoreApi/Routes/V1/AI/BusinessDescription.php index f377eff9a6f..1a30a9f503d 100644 --- a/src/StoreApi/Routes/V1/AI/BusinessDescription.php +++ b/src/StoreApi/Routes/V1/AI/BusinessDescription.php @@ -2,7 +2,6 @@ namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; -use Automattic\WooCommerce\Blocks\Patterns\ProductUpdater; use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute; /** diff --git a/src/StoreApi/Routes/V1/AI/StoreTitle.php b/src/StoreApi/Routes/V1/AI/StoreTitle.php new file mode 100644 index 00000000000..353a29a0b45 --- /dev/null +++ b/src/StoreApi/Routes/V1/AI/StoreTitle.php @@ -0,0 +1,146 @@ + \WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'get_response' ], + 'permission_callback' => [ Middleware::class, 'is_authorized' ], + 'args' => [ + 'business_description' => [ + 'description' => __( 'The business description for a given store.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + ], + ], + ], + 'schema' => [ $this->schema, 'get_public_item_schema' ], + 'allow_batch' => [ 'v1' => true ], + ]; + } + + /** + * Update the store title powered by AI. + * + * @param \WP_REST_Request $request Request object. + * + * @return bool|string|\WP_Error|\WP_REST_Response + */ + protected function get_route_post_response( \WP_REST_Request $request ) { + + $business_description = $request->get_param( 'business_description' ); + + if ( ! $business_description ) { + return $this->error_to_response( + new \WP_Error( + 'invalid_business_description', + __( 'Invalid business description.', 'woo-gutenberg-products-block' ) + ) + ); + } + + $store_title = get_option( 'blogname' ); + if ( ! ( empty( $store_title ) || self::DEFAULT_TITLE === $store_title ) ) { + return rest_ensure_response( array( 'ai_content_generated' => false ) ); + } + + $ai_generated_title = $this->generate_ai_title( $business_description ); + if ( is_wp_error( $ai_generated_title ) ) { + return $this->error_to_response( $ai_generated_title ); + } + + update_option( self::STORE_TITLE_OPTION_NAME, $ai_generated_title ); + + return rest_ensure_response( + array( + 'ai_content_generated' => true, + ) + ); + } + + /** + * Generate the store title powered by AI. + * + * @param string $business_description The business description for a given store. + * + * @return string|\WP_Error|\WP_REST_Response The store title generated by AI. + */ + private function generate_ai_title( $business_description ) { + $ai_connection = new Connection(); + + $site_id = $ai_connection->get_site_id(); + if ( is_wp_error( $site_id ) ) { + return $this->error_to_response( $site_id ); + } + + $token = $ai_connection->get_jwt_token( $site_id ); + if ( is_wp_error( $token ) ) { + return $this->error_to_response( $token ); + } + + $prompt = "Generate a store title for a store that has the following: '$business_description'. The length of the title should be 1 and 3 words. The result should include only the store title without any other explanation, number or punctuation marks"; + + $ai_response = $ai_connection->fetch_ai_response( $token, $prompt ); + if ( is_wp_error( $ai_response ) ) { + return $this->error_to_response( $ai_response ); + } + + if ( ! isset( $ai_response['completion'] ) ) { + return ''; + } + + return $ai_response['completion']; + } +} diff --git a/src/StoreApi/RoutesController.php b/src/StoreApi/RoutesController.php index aace314ca39..de734454396 100644 --- a/src/StoreApi/RoutesController.php +++ b/src/StoreApi/RoutesController.php @@ -61,10 +61,11 @@ public function __construct( SchemaController $schema_controller ) { ], // @todo Migrate internal AI routes to WooCommerce Core codebase. 'private' => [ - Routes\V1\AI\Images::IDENTIFIER => Routes\V1\AI\Images::class, - Routes\V1\AI\Patterns::IDENTIFIER => Routes\V1\AI\Patterns::class, - Routes\V1\AI\Product::IDENTIFIER => Routes\V1\AI\Product::class, - Routes\V1\AI\Products::IDENTIFIER => Routes\V1\AI\Products::class, + Routes\V1\AI\StoreTitle::IDENTIFIER => Routes\V1\AI\StoreTitle::class, + Routes\V1\AI\Images::IDENTIFIER => Routes\V1\AI\Images::class, + Routes\V1\AI\Patterns::IDENTIFIER => Routes\V1\AI\Patterns::class, + Routes\V1\AI\Product::IDENTIFIER => Routes\V1\AI\Product::class, + Routes\V1\AI\Products::IDENTIFIER => Routes\V1\AI\Products::class, Routes\V1\AI\BusinessDescription::IDENTIFIER => Routes\V1\AI\BusinessDescription::class, ], ]; diff --git a/src/StoreApi/SchemaController.php b/src/StoreApi/SchemaController.php index bc1a284ca7e..f836f7ce4e6 100644 --- a/src/StoreApi/SchemaController.php +++ b/src/StoreApi/SchemaController.php @@ -54,6 +54,7 @@ public function __construct( ExtendSchema $extend ) { Schemas\V1\ProductCategorySchema::IDENTIFIER => Schemas\V1\ProductCategorySchema::class, Schemas\V1\ProductCollectionDataSchema::IDENTIFIER => Schemas\V1\ProductCollectionDataSchema::class, Schemas\V1\ProductReviewSchema::IDENTIFIER => Schemas\V1\ProductReviewSchema::class, + Schemas\V1\AI\StoreTitleSchema::IDENTIFIER => Schemas\V1\AI\StoreTitleSchema::class, Schemas\V1\AI\ImagesSchema::IDENTIFIER => Schemas\V1\AI\ImagesSchema::class, Schemas\V1\AI\PatternsSchema::IDENTIFIER => Schemas\V1\AI\PatternsSchema::class, Schemas\V1\AI\ProductSchema::IDENTIFIER => Schemas\V1\AI\ProductSchema::class, diff --git a/src/StoreApi/Schemas/V1/AI/StoreTitleSchema.php b/src/StoreApi/Schemas/V1/AI/StoreTitleSchema.php new file mode 100644 index 00000000000..4310ca5044d --- /dev/null +++ b/src/StoreApi/Schemas/V1/AI/StoreTitleSchema.php @@ -0,0 +1,47 @@ + true, + ]; + } +} From bf2ce9e6b5b931f99113dddef9feddd31ba5af61 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Fri, 24 Nov 2023 21:31:02 +0100 Subject: [PATCH 04/11] Product Gallery Thumbnails: Fix overflow issues and improve responsiveness (#11665) * Product Gallery Thumbnails: Refactor sizing in the editor and the front end * Product Gallery Thumbnails: Change default vertical alignment to top and better control the width of the thumbnails * Product Gallery Thumbnails: Restrict the bottom position thumbnails width based on the total number of thumbnails set * Product Gallery: Remove hardcoded width for Thumbnails and the Large Image and update the width inside of the Dialog * Product Gallery Thumbnails: Introduce thumbnails scaling based on the number of thumbnails * Product Gallery Thumbnails: Fix editor thumbnails scaling * Product Gallery Thumbnails: Remove unused column gap variable * Product Gallery Thumbnails: Fix styling for vertical images * Product Gallery: Remove the unused editor.scss file * Product Gallery: Fix the placement of the Thumbnails block in the block template * Product Gallery Dialog: Reset changes to the dialog * update @wordpress/e2e-test-utils-playwright package * don't update node version * remove waitForSiteEditorFinishLoading function * use visitSiteEditor util * Product Gallery Thumbnails: Add code comments * Product Gallery Thumbnails E2E: Fix the test checking the default position of the thumbnails * Product Gallery E2E: Fix the test checking if the cropping setting works correctly * Product Gallery Thumbnails: Hide the Thumbnails block if there aren't at least 2 thumbnails to display --------- Co-authored-by: Paulo Arromba <17236129+wavvves@users.noreply.github.com> Co-authored-by: Luigi Teschio --- assets/js/blocks/product-gallery/edit.tsx | 12 ++- assets/js/blocks/product-gallery/editor.scss | 5 -- .../product-gallery-large-image/editor.scss | 2 + .../block-settings/index.tsx | 4 +- .../product-gallery-thumbnails/edit.tsx | 25 +++--- .../product-gallery-thumbnails/editor.scss | 39 ++++++--- assets/js/blocks/product-gallery/style.scss | 81 +++++++++++++++++-- src/BlockTypes/ProductGalleryThumbnails.php | 4 +- ...humbnails.block_theme.side_effects.spec.ts | 78 +++++++++++------- ...t-gallery.block_theme.side_effects.spec.ts | 4 +- 10 files changed, 183 insertions(+), 71 deletions(-) delete mode 100644 assets/js/blocks/product-gallery/editor.scss diff --git a/assets/js/blocks/product-gallery/edit.tsx b/assets/js/blocks/product-gallery/edit.tsx index 9264f5b07ec..6aeeb77d1f0 100644 --- a/assets/js/blocks/product-gallery/edit.tsx +++ b/assets/js/blocks/product-gallery/edit.tsx @@ -25,7 +25,13 @@ import type { ProductGalleryAttributes } from './types'; const TEMPLATE: InnerBlockTemplate[] = [ [ 'core/group', - { layout: { type: 'flex', flexWrap: 'nowrap' } }, + { + layout: { + type: 'flex', + flexWrap: 'nowrap', + verticalAlignment: 'top', + }, + }, [ [ 'woocommerce/product-gallery-thumbnails', @@ -38,6 +44,10 @@ const TEMPLATE: InnerBlockTemplate[] = [ type: 'flex', orientation: 'vertical', justifyContent: 'center', + verticalAlignment: 'top', + }, + style: { + layout: { selfStretch: 'fixed', flexSize: '100%' }, }, ...getInnerBlocksLockAttributes( 'lock' ), }, diff --git a/assets/js/blocks/product-gallery/editor.scss b/assets/js/blocks/product-gallery/editor.scss deleted file mode 100644 index 13116d7fc2d..00000000000 --- a/assets/js/blocks/product-gallery/editor.scss +++ /dev/null @@ -1,5 +0,0 @@ -.wc-block-product-gallery { - .block-editor-inner-blocks { - width: fit-content; - } -} diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/editor.scss b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/editor.scss index 65b0e5b7bf6..1c3198620cc 100644 --- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/editor.scss +++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/editor.scss @@ -1,4 +1,6 @@ .wc-block-editor-product-gallery-large-image { + width: 100%; + img { max-width: 100%; margin: 0 auto; diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block-settings/index.tsx b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block-settings/index.tsx index cdf4e133cfb..640497103c4 100644 --- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block-settings/index.tsx +++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block-settings/index.tsx @@ -51,7 +51,7 @@ export const ProductGalleryThumbnailsBlockSettings = ( { context, }: ProductGalleryThumbnailsSettingsProps ) => { const maxNumberOfThumbnails = 8; - const minNumberOfThumbnails = 2; + const minNumberOfThumbnails = 3; const { productGalleryClientId } = context; // @ts-expect-error @wordpress/block-editor/store types not provided const { updateBlockAttributes } = useDispatch( blockEditorStore ); @@ -110,7 +110,7 @@ export const ProductGalleryThumbnailsBlockSettings = ( { } ) } help={ __( - 'Choose how many thumbnails (2-8) will display. If more images exist, a “View all” button will display.', + 'Choose how many thumbnails (3-8) will display. If more images exist, a “View all” button will display.', 'woo-gutenberg-products-block' ) } max={ maxNumberOfThumbnails } diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/edit.tsx b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/edit.tsx index d62da00ad1a..b925d005f05 100644 --- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/edit.tsx +++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/edit.tsx @@ -25,26 +25,29 @@ interface EditProps export const Edit = ( { attributes, setAttributes, context }: EditProps ) => { const blockProps = useBlockProps( { - className: 'wc-block-product-gallery-thumbnails', + className: classNames( + 'wc-block-product-gallery-thumbnails', + `wc-block-product-gallery-thumbnails--number-of-thumbnails-${ context.thumbnailsNumberOfThumbnails }`, + `wc-block-product-gallery-thumbnails--position-${ context.thumbnailsPosition }` + ), } ); const Placeholder = () => { return context.thumbnailsPosition !== ThumbnailsPosition.OFF ? ( -
+
{ [ ...Array( context.thumbnailsNumberOfThumbnails ).keys(), ].map( ( index ) => { return ( - Placeholder + > + Placeholder +
); } ) }
diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/editor.scss b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/editor.scss index 006c477d7f1..a789d44745b 100644 --- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/editor.scss +++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/editor.scss @@ -1,17 +1,30 @@ -.wc-block-product-gallery-thumbnails { - width: fit-content; - .wc-block-editor-product-gallery-thumbnails { - display: flex; - flex-flow: column nowrap; +$thumbnails: ".wc-block-editor-product-gallery-thumbnails"; +$thumbnails-gap: 15px; - &.wc-block-editor-product-gallery-thumbnails--bottom { - flex-flow: row nowrap; - } +#{$thumbnails} { + display: flex; - img { - width: 100px; - height: 100px; - margin: 5px; - } + .wc-block-product-gallery-thumbnails--position-bottom & { + flex-direction: row; + gap: 0 15px; + } + + .wc-block-product-gallery-thumbnails:not(.wc-block-product-gallery-thumbnails--position-bottom) & { + flex-direction: column; + gap: 15px 0; + } +} + +@for $i from 3 through 8 { + // Calculate the total width occupied by the gaps between thumbnails. + $gap-width: $thumbnails-gap * ($i - 1); + + // Calculate the border width, which is multiplied by 2 to account for both sides of each thumbnail. + $border-width: ($i * 1px * 2); + + $additional-space: $i * 1px; + + .wc-block-product-gallery-thumbnails--number-of-thumbnails-#{$i}:not(.wc-block-product-gallery-thumbnails--position-bottom) { + flex-basis: calc((100% - #{$gap-width} - #{$border-width} - #{$additional-space}) / #{$i}); } } diff --git a/assets/js/blocks/product-gallery/style.scss b/assets/js/blocks/product-gallery/style.scss index f73e0d735cd..191bc88bf4b 100644 --- a/assets/js/blocks/product-gallery/style.scss +++ b/assets/js/blocks/product-gallery/style.scss @@ -10,6 +10,8 @@ $gallery-next-previous-inside-image: "#{$gallery}:not([data-next-previous-button $outside-image-offset: 30px; $outside-image-max-width: calc(100% - (2 * $outside-image-offset)); +$thumbnails-gap: 15px; +$default-number-of-thumbnails: 3; // Product Gallery #{$gallery} { @@ -55,6 +57,7 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset)); height: fit-content; position: relative; overflow: hidden; + flex-grow: 1; // Restrict the width of the Large Image when the Next/Previous buttons are outside the image. #{$gallery-next-previous-outside-image} & .wc-block-product-gallery-large-image__image-element { @@ -64,10 +67,13 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset)); } .wc-block-product-gallery-large-image__wrapper { + aspect-ratio: 1 / 1; flex-shrink: 0; max-width: 100%; overflow: hidden; width: 100%; + display: flex; + align-items: center; } .wc-block-product-gallery-large-image__container { @@ -93,7 +99,8 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset)); margin: 0 auto; z-index: 1; transition: all 0.1s linear; - width: auto; + aspect-ratio: 1 / 1; + object-fit: contain; // Keep the order in this way. The hoverZoom class should override the full-screen-on-click class when both are applied. &.wc-block-woocommerce-product-gallery-large-image__image--full-screen-on-click { @@ -234,20 +241,82 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset)); // Thumbnails #{$thumbnails} { + display: flex; + img { cursor: pointer; + height: auto; + width: auto; + max-width: 100%; } - .is-vertical & { - display: flex; + #{$gallery}[data-thumbnails-position='bottom'] & { flex-direction: row; + gap: 0 15px; + } + + #{$gallery}:not([data-thumbnails-position='bottom']) & { + flex-direction: column; + gap: 15px 0; + + // Calculate the total width occupied by the gaps between thumbnails. + $gap-width: $thumbnails-gap * ($default-number-of-thumbnails - 1); + + // Calculate the border width, which is multiplied by 2 to account for both sides of each thumbnail. + $border-width: #{$default-number-of-thumbnails * 1px * 2}; + + // Calculate the width of each thumbnail by accounting for the gap, border, and additional space. + flex-basis: calc((100% - #{$gap-width} - #{$border-width} - 4px) / #{$default-number-of-thumbnails}); + } + + @for $i from 3 through 8 { + #{$gallery}[data-thumbnails-number-of-thumbnails='#{$i}']:not([data-thumbnails-position='bottom']) & { + // Calculate the total width occupied by the gaps between thumbnails. + $gap-width: $thumbnails-gap * ($i - 1); + + // Calculate the border width, which is multiplied by 2 to account for both sides of each thumbnail. + $border-width: $i * 1px * 2; + + flex-basis: calc((100% - #{$gap-width} - #{$border-width}) / $i); + } } .wc-block-product-gallery-thumbnails__thumbnail { - width: 100px; - height: 100px; - margin: 5px; + border: 1px solid rgba($color: #000, $alpha: 0.1); + height: auto; + width: auto; + display: flex; + justify-content: center; + align-items: center; + aspect-ratio: 1 / 1; position: relative; + flex-basis: 0; + flex-grow: 1; + + img { + aspect-ratio: 1 / 1; + object-fit: contain; + } + + &::before { + content: ""; + display: block; + padding-top: 100%; + } + + @for $i from 3 through 8 { + #{$gallery}[data-thumbnails-number-of-thumbnails='#{$i}'][data-thumbnails-position="bottom"] & { + // Calculate the total width occupied by the gaps between thumbnails. + $gap-width: $thumbnails-gap * ($i - 1); + + // Calculate the border width, which is multiplied by 2 to account for both sides of each thumbnail. + $border-width: $i * 1px * 2; + + $thumbnail-width: calc((100% - #{$gap-width} - #{$border-width}) / $i); + + flex: 0 0 $thumbnail-width; + } + } } diff --git a/src/BlockTypes/ProductGalleryThumbnails.php b/src/BlockTypes/ProductGalleryThumbnails.php index 3704b0f93a4..13d9f4a0e87 100644 --- a/src/BlockTypes/ProductGalleryThumbnails.php +++ b/src/BlockTypes/ProductGalleryThumbnails.php @@ -133,8 +133,8 @@ protected function render( $attributes, $content, $block ) { if ( $product ) { $post_thumbnail_id = $product->get_image_id(); - $product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'thumbnail', array(), 'wc-block-product-gallery-thumbnails__thumbnail' ); - if ( $product_gallery_images && $post_thumbnail_id ) { + $product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'full', array(), 'wc-block-product-gallery-thumbnails__thumbnail' ); + if ( $product_gallery_images && count( $product_gallery_images ) > 1 && $post_thumbnail_id ) { $html = ''; $number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3; $mode = $block->context['mode'] ?? ''; diff --git a/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts b/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts index 1b93c340685..f749265e177 100644 --- a/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts +++ b/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts @@ -52,6 +52,8 @@ test.describe( `${ blockData.name }`, () => { page, editor, pageObject, + editorUtils, + frontendUtils, } ) => { await editor.insertBlock( { name: 'woocommerce/product-gallery', @@ -60,22 +62,38 @@ test.describe( `${ blockData.name }`, () => { const thumbnailsBlock = await pageObject.getThumbnailsBlock( { page: 'editor', } ); - const largeImageBlock = await pageObject.getMainImageBlock( { - page: 'editor', - } ); - - const thumbnailsBlockBoundingRect = await thumbnailsBlock.boundingBox(); - const largeImageBlockBoundingRect = await largeImageBlock.boundingBox(); await expect( thumbnailsBlock ).toBeVisible(); - // Check the default position: on the left of the large image - await expect( thumbnailsBlockBoundingRect?.y ).toBeGreaterThan( - largeImageBlockBoundingRect?.y as number + + // We should refactor this. + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout( 500 ); + + // Test the default (left) position of thumbnails by cross-checking: + // - The Gallery block has the classes "is-layout-flex" and "is-nowrap". + // - The Thumbnails block has a lower index than the Large Image block. + + const groupBlock = ( + await editorUtils.getBlockByTypeWithParent( + 'core/group', + 'woocommerce/product-gallery' + ) + ).first(); + + const groupBlockClassAttribute = await groupBlock.getAttribute( + 'class' ); - await expect( thumbnailsBlockBoundingRect?.x ).toBeLessThan( - largeImageBlockBoundingRect?.x as number + expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' ); + expect( groupBlockClassAttribute ).toContain( 'is-nowrap' ); + + const isThumbnailsBlockEarlier = await editorUtils.isBlockEarlierThan( + groupBlock, + 'woocommerce/product-gallery-thumbnails', + 'core/group' ); + expect( isThumbnailsBlockEarlier ).toBe( true ); + await Promise.all( [ editor.saveSiteEditorEntities(), page.waitForResponse( ( response ) => @@ -87,27 +105,27 @@ test.describe( `${ blockData.name }`, () => { waitUntil: 'commit', } ); - const thumbnailsBlockFrontend = await pageObject.getThumbnailsBlock( { - page: 'frontend', - } ); - - const largeImageBlockFrontend = await pageObject.getMainImageBlock( { - page: 'frontend', - } ); + const groupBlockFrontend = ( + await frontendUtils.getBlockByClassWithParent( + 'wp-block-group', + 'woocommerce/product-gallery' + ) + ).first(); + + const groupBlockFrontendClassAttribute = + await groupBlockFrontend.getAttribute( 'class' ); + expect( groupBlockFrontendClassAttribute ).toContain( + 'is-layout-flex' + ); + expect( groupBlockFrontendClassAttribute ).toContain( 'is-nowrap' ); - const thumbnailsBlockFrontendBoundingRect = - await thumbnailsBlockFrontend.boundingBox(); - const largeImageBlockFrontendBoundingRect = - await largeImageBlockFrontend.boundingBox(); + const isThumbnailsFrontendBlockEarlier = + await frontendUtils.isBlockEarlierThanGroupBlock( + groupBlockFrontend, + 'woocommerce/product-gallery-thumbnails' + ); - await expect( thumbnailsBlockFrontend ).toBeVisible(); - // Check the default position: on the left of the large image - await expect( thumbnailsBlockFrontendBoundingRect?.y ).toBeGreaterThan( - largeImageBlockFrontendBoundingRect?.y as number - ); - await expect( thumbnailsBlockFrontendBoundingRect?.x ).toBeLessThan( - largeImageBlockFrontendBoundingRect?.x as number - ); + expect( isThumbnailsFrontendBlockEarlier ).toBe( true ); } ); test.describe( `${ blockData.name } Settings`, () => { diff --git a/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts b/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts index aff6c83330c..e4a051a4276 100644 --- a/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts +++ b/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts @@ -327,6 +327,8 @@ test.describe( `${ blockData.name }`, () => { const width = image?.width; // Allow 1 pixel of difference. - expect( width === height + 1 || width === height - 1 ).toBeTruthy(); + expect( + width === height + 1 || width === height - 1 || width === height + ).toBeTruthy(); } ); } ); From dcc3837826a651004286bd40d0d88f625795dc8e Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Fri, 24 Nov 2023 23:49:44 +0100 Subject: [PATCH 05/11] Product Gallery Thumbnails: Add support for cropping (#11718) * Product Gallery Thumbnails: Refactor sizing in the editor and the front end * Product Gallery Thumbnails: Change default vertical alignment to top and better control the width of the thumbnails * Product Gallery Thumbnails: Fix thumbnails cropping based on the 'Crop images to fit' setting * Product Gallery Thumbnails: Revert thumbnails styling from #11665 * Product Gallery Thumbnails: Update the default value of the cropImages setting to false --- .../inner-blocks/product-gallery-thumbnails/block.json | 2 +- src/BlockTypes/ProductGalleryThumbnails.php | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block.json b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block.json index 4cb598b27f2..a4768bd8f66 100644 --- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block.json +++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block.json @@ -7,7 +7,7 @@ "description": "Display the Thumbnails of a product.", "category": "woocommerce", "keywords": [ "WooCommerce" ], - "usesContext": [ "postId", "thumbnailsPosition", "thumbnailsNumberOfThumbnails", "productGalleryClientId", "mode" ], + "usesContext": [ "postId", "thumbnailsPosition", "thumbnailsNumberOfThumbnails", "productGalleryClientId", "mode", "cropImages" ], "textdomain": "woo-gutenberg-products-block", "ancestor": [ "woocommerce/product-gallery" ], "supports": { diff --git a/src/BlockTypes/ProductGalleryThumbnails.php b/src/BlockTypes/ProductGalleryThumbnails.php index 13d9f4a0e87..35ddcaa6d91 100644 --- a/src/BlockTypes/ProductGalleryThumbnails.php +++ b/src/BlockTypes/ProductGalleryThumbnails.php @@ -37,7 +37,7 @@ protected function get_block_type_style() { * @return string[] */ protected function get_block_type_uses_context() { - return [ 'productGalleryClientId', 'postId', 'thumbnailsNumberOfThumbnails', 'thumbnailsPosition', 'mode' ]; + return [ 'productGalleryClientId', 'postId', 'thumbnailsNumberOfThumbnails', 'thumbnailsPosition', 'mode', 'cropImages' ]; } /** @@ -133,7 +133,9 @@ protected function render( $attributes, $content, $block ) { if ( $product ) { $post_thumbnail_id = $product->get_image_id(); - $product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'full', array(), 'wc-block-product-gallery-thumbnails__thumbnail' ); + $crop_images = $block->context['cropImages'] ?? false; + $product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'full', array(), 'wc-block-product-gallery-thumbnails__thumbnail', $crop_images ); + if ( $product_gallery_images && count( $product_gallery_images ) > 1 && $post_thumbnail_id ) { $html = ''; $number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3; From 234a289c18c0240b6e5ab1541321795933775a1f Mon Sep 17 00:00:00 2001 From: Sam Seay Date: Mon, 27 Nov 2023 12:26:01 +0800 Subject: [PATCH 06/11] Migrate interactivity stock filter to new store API, add improvements and bugfixes (#11827) --- .../inner-blocks/stock-filter/frontend.ts | 82 ++++------ assets/js/interactivity/directives.js | 13 +- .../dropdown/index.ts | 140 +++++++++--------- src/BlockTypes/CollectionStockFilter.php | 130 ++++++++-------- src/InteractivityComponents/Dropdown.php | 85 ++++++----- 5 files changed, 216 insertions(+), 234 deletions(-) diff --git a/assets/js/blocks/collection-filters/inner-blocks/stock-filter/frontend.ts b/assets/js/blocks/collection-filters/inner-blocks/stock-filter/frontend.ts index ec6e2e54c47..e82ce9ec9c6 100644 --- a/assets/js/blocks/collection-filters/inner-blocks/stock-filter/frontend.ts +++ b/assets/js/blocks/collection-filters/inner-blocks/stock-filter/frontend.ts @@ -1,10 +1,7 @@ /** * External dependencies */ -import { - store as interactivityStore, - navigate, -} from '@woocommerce/interactivity'; +import { store, navigate, getContext } from '@woocommerce/interactivity'; import { DropdownContext } from '@woocommerce/interactivity-components/dropdown'; import { HTMLElementEvent } from '@woocommerce/types'; @@ -21,59 +18,40 @@ const getUrl = ( activeFilters: string ) => { return url.href; }; -type StockFilterState = { - filters: { - stockStatus: string; - activeFilters: string; - showDropdown: boolean; - }; -}; - -type ActionProps = { - state: StockFilterState; - event: HTMLElementEvent< HTMLInputElement >; -}; +store( 'woocommerce/collection-stock-filter', { + actions: { + // "on select" handler passed to the dropdown component. + navigate: () => { + const context = getContext< DropdownContext >( + 'woocommerce/interactivity-dropdown' + ); -interactivityStore( { - state: { - filters: { - stockStatus: '', + navigate( getUrl( context.selectedItem.value || '' ) ); }, - }, - actions: { - filters: { - navigate: ( { context }: { context: DropdownContext } ) => { - if ( context.woocommerceDropdown.selectedItem.value ) { - navigate( - getUrl( context.woocommerceDropdown.selectedItem.value ) - ); + updateProducts: ( event: HTMLElementEvent< HTMLInputElement > ) => { + // get the active filters from the url: + const url = new URL( window.location.href ); + const currentFilters = + url.searchParams.get( 'filter_stock_status' ) || ''; + + // split out the active filters into an array. + const filtersArr = + currentFilters === '' ? [] : currentFilters.split( ',' ); + + // if checked and not already in activeFilters, add to activeFilters + // if not checked and in activeFilters, remove from activeFilters. + if ( event.target.checked ) { + if ( ! currentFilters.includes( event.target.value ) ) { + filtersArr.push( event.target.value ); } - }, - updateProducts: ( { event }: ActionProps ) => { - // get the active filters from the url: - const url = new URL( window.location.href ); - const currentFilters = - url.searchParams.get( 'filter_stock_status' ) || ''; - - // split out the active filters into an array. - const filtersArr = - currentFilters === '' ? [] : currentFilters.split( ',' ); - - // if checked and not already in activeFilters, add to activeFilters - // if not checked and in activeFilters, remove from activeFilters. - if ( event.target.checked ) { - if ( ! currentFilters.includes( event.target.value ) ) { - filtersArr.push( event.target.value ); - } - } else { - const index = filtersArr.indexOf( event.target.value ); - if ( index > -1 ) { - filtersArr.splice( index, 1 ); - } + } else { + const index = filtersArr.indexOf( event.target.value ); + if ( index > -1 ) { + filtersArr.splice( index, 1 ); } + } - navigate( getUrl( filtersArr.join( ',' ) ) ); - }, + navigate( getUrl( filtersArr.join( ',' ) ) ); }, }, } ); diff --git a/assets/js/interactivity/directives.js b/assets/js/interactivity/directives.js index 9cd33f52d23..9e659851492 100644 --- a/assets/js/interactivity/directives.js +++ b/assets/js/interactivity/directives.js @@ -100,9 +100,18 @@ export default () => { // data-wc-on--[event] directive( 'on', ( { directives: { on }, element, evaluate } ) => { + const events = new Map(); on.forEach( ( entry ) => { - element.props[ `on${ entry.suffix }` ] = ( event ) => { - evaluate( entry, event ); + const event = entry.suffix.split( '--' )[ 0 ]; + if ( ! events.has( event ) ) events.set( event, new Set() ); + events.get( event ).add( entry ); + } ); + + events.forEach( ( entries, event ) => { + element.props[ `on${ event }` ] = ( event ) => { + entries.forEach( ( entry ) => { + evaluate( entry, event ); + } ); }; } ); } ); diff --git a/packages/interactivity-components/dropdown/index.ts b/packages/interactivity-components/dropdown/index.ts index 69b7180ea4b..1285048ac2b 100644 --- a/packages/interactivity-components/dropdown/index.ts +++ b/packages/interactivity-components/dropdown/index.ts @@ -1,95 +1,93 @@ /** * External dependencies */ -import { store as interactivityStore } from '@woocommerce/interactivity'; +import { getContext, store } from '@woocommerce/interactivity'; export type DropdownContext = { - woocommerceDropdown: { - currentItem: { - label: string; - value: string; - }; - selectedItem: { - label: string | null; - value: string | null; - }; - hoveredItem: { - label: string | null; - value: string | null; - }; - isOpen: boolean; + currentItem: { + label: string; + value: string; }; + selectedItem: { + label: string | null; + value: string | null; + }; + hoveredItem: { + label: string | null; + value: string | null; + }; + isOpen: boolean; }; -type Store = { - context: DropdownContext; - selectors: unknown; - ref: HTMLElement; -}; - -interactivityStore( { +store( 'woocommerce/interactivity-dropdown', { state: {}, selectors: { - woocommerceDropdown: { - placeholderText: ( { context }: { context: DropdownContext } ) => { - const { - woocommerceDropdown: { selectedItem }, - } = context; + placeholderText: () => { + const context = getContext< DropdownContext >(); + const { selectedItem } = context; - return selectedItem.label || 'Select an option'; - }, - isSelected: ( { context }: { context: DropdownContext } ) => { - const { - woocommerceDropdown: { - currentItem: { value }, - }, - } = context; + return selectedItem.label || 'Select an option'; + }, + isSelected: () => { + const context = getContext< DropdownContext >(); + + const { + currentItem: { value }, + } = context; - return ( - context.woocommerceDropdown.selectedItem.value === value || - context.woocommerceDropdown.hoveredItem.value === value - ); - }, + return ( + context.selectedItem.value === value || + context.hoveredItem.value === value + ); }, }, actions: { - woocommerceDropdown: { - toggleIsOpen: ( store: Store ) => { - const { - context: { woocommerceDropdown }, - } = store; + toggleIsOpen: () => { + const context = getContext< DropdownContext >(); + + context.isOpen = ! context.isOpen; + }, + selectDropdownItem: ( event: MouseEvent ) => { + const context = getContext< DropdownContext >(); - woocommerceDropdown.isOpen = ! woocommerceDropdown.isOpen; - }, - selectDropdownItem: ( { - context, - }: { - context: DropdownContext; - } ) => { - const { - woocommerceDropdown: { - currentItem: { label, value }, - }, - } = context; + const { + currentItem: { label, value }, + } = context; - context.woocommerceDropdown.selectedItem = { label, value }; - context.woocommerceDropdown.isOpen = false; - }, - addHoverClass: ( { context }: { context: DropdownContext } ) => { - const { - woocommerceDropdown: { - currentItem: { label, value }, - }, - } = context; + const { selectedItem } = context; - context.woocommerceDropdown.hoveredItem = { label, value }; - }, - removeHoverClass: ( { context }: { context: DropdownContext } ) => { - context.woocommerceDropdown.hoveredItem = { + if ( + selectedItem.value === value && + selectedItem.label === label + ) { + context.selectedItem = { label: null, value: null, }; - }, + } else { + context.selectedItem = { label, value }; + } + + context.isOpen = false; + + event.stopPropagation(); + }, + addHoverClass: () => { + const context = getContext< DropdownContext >(); + + const { + currentItem: { label, value }, + } = context; + + context.hoveredItem = { label, value }; + }, + removeHoverClass: () => { + const context = getContext< DropdownContext >(); + + context.hoveredItem = { + label: null, + value: null, + }; }, }, } ); diff --git a/src/BlockTypes/CollectionStockFilter.php b/src/BlockTypes/CollectionStockFilter.php index 2fc9c47beb4..ef672bf1cf0 100644 --- a/src/BlockTypes/CollectionStockFilter.php +++ b/src/BlockTypes/CollectionStockFilter.php @@ -47,17 +47,6 @@ protected function render( $attributes, $content, $block ) { $stock_status_counts = $block->context['collectionData']['stock_status_counts'] ?? []; $wrapper_attributes = get_block_wrapper_attributes(); - wc_store( - array( - 'state' => array( - 'filters' => array( - 'stockStatus' => $stock_status_counts, - 'activeFilters' => '', - ), - ), - ) - ); - return sprintf( '
%2$s
@@ -77,6 +66,7 @@ protected function render( $attributes, $content, $block ) { */ private function get_stock_filter_html( $stock_counts, $attributes ) { $display_style = $attributes['displayStyle'] ?? 'list'; + $show_counts = $attributes['showCounts'] ?? false; $stock_statuses = wc_get_product_stock_status_options(); // check the url params to select initial item on page load. @@ -84,9 +74,10 @@ private function get_stock_filter_html( $stock_counts, $attributes ) { $selected_stock_status = isset( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ) : ''; $list_items = array_map( - function( $item ) use ( $stock_statuses ) { + function( $item ) use ( $stock_statuses, $show_counts ) { + $label = $show_counts ? $stock_statuses[ $item['status'] ] . ' (' . $item['count'] . ')' : $stock_statuses[ $item['status'] ]; return array( - 'label' => $stock_statuses[ $item['status'] ], + 'label' => $label, 'value' => $item['status'], ); }, @@ -108,63 +99,70 @@ function( $item ) use ( $selected_stock_status ) { 'value' => null, ); + $data_directive = wp_json_encode( array( 'namespace' => 'woocommerce/collection-stock-filter' ) ); + ob_start(); ?> - -
-
    - -
  • -
    -
  • + +
+
+ + + + $list_items, + 'action' => 'woocommerce/collection-stock-filter::actions.navigate', + 'selected_item' => $selected_item, + ) + ); + ?> + +
array( - 'selectedItem' => $selected_item, - 'hoveredItem' => array( - 'label' => null, - 'value' => null, - ), - 'isOpen' => false, + 'selectedItem' => $selected_item, + 'hoveredItem' => array( + 'label' => null, + 'value' => null, ), + 'isOpen' => false, ); $action = $props['action'] ?? ''; @@ -40,45 +38,46 @@ public static function render( $props ) { ob_start(); ?> -
-
-
-
- - + + +
+
+ + +
- - - - - + Date: Mon, 27 Nov 2023 12:27:04 +0530 Subject: [PATCH 07/11] [Product Collection] Fix: HTML entity decoding for product names in Hand-Picked Products (#11927) * Add HTML entity decoding for product names in Hand-Picked Products control In the Hand-Picked Products control within the product-collection inspector controls, a function for decoding HTML entities in product names has been added. - A new utility function `decodeHTMLEntities` has been implemented. This function decodes HTML entities in a string, ensuring that special characters are correctly displayed in their human-readable form. - The `transformTokenIntoProductName` function has been updated to utilize `decodeHTMLEntities`. Now, when a product name is fetched (either directly as a token or via a product ID), the HTML entities within the name are decoded. - This enhancement ensures that product names containing characters like ampersands or other HTML entities are accurately displayed in the UI. This change improves the readability and accuracy of product names within the Hand-Picked Products control, enhancing the user experience for store managers using WooCommerce Blocks. * Update label and hide description This commit updates the `HandPickedProductsControl` component. Specifically, the user-facing label for product selection has been changed from 'Pick some products' to 'Hand-picked Products'. Additionally, the `__experimentalShowHowTo` property has been added with a `false` value, to hide description. Corresponding changes have been made in the E2E test file `product-collection.block_theme.spec.ts`, where the filter name is updated to match the new label. * Refactor: Replace custom HTML entity decoder with `@wordpress/html-entities` Rationale: - The shift to `@wordpress/html-entities` aligns with standard WordPress practices, ensuring consistency across the platform. - Enhances maintainability by relying on a well-supported library rather than custom code. - Simplifies the codebase by removing a redundant utility function. This change enhances the robustness of our code and aligns with best practices in WordPress development. --- .../hand-picked-products-control.tsx | 13 ++++++++++--- .../product-collection.block_theme.spec.ts | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/assets/js/blocks/product-collection/inspector-controls/hand-picked-products-control.tsx b/assets/js/blocks/product-collection/inspector-controls/hand-picked-products-control.tsx index f95e9ee7653..b0d14e6b774 100644 --- a/assets/js/blocks/product-collection/inspector-controls/hand-picked-products-control.tsx +++ b/assets/js/blocks/product-collection/inspector-controls/hand-picked-products-control.tsx @@ -3,6 +3,7 @@ */ import { getProducts } from '@woocommerce/editor-components/utils'; import { ProductResponseItem } from '@woocommerce/types'; +import { decodeEntities } from '@wordpress/html-entities'; import { useState, useEffect, useCallback, useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { @@ -91,16 +92,21 @@ const HandPickedProductsControl = ( { ); }, [ productsList, selectedProductIds ] ); + /** + * Transforms a token into a product name. + * - If the token is a number, it will be used to lookup the product name. + * - Otherwise, the token will be used as is. + */ const transformTokenIntoProductName = ( token: string ) => { const parsedToken = Number( token ); if ( Number.isNaN( parsedToken ) ) { - return token; + return decodeEntities( token ) || ''; } const product = productsMap.get( parsedToken ); - return product?.name || ''; + return decodeEntities( product?.name ) || ''; }; return ( @@ -120,7 +126,7 @@ const HandPickedProductsControl = ( { disabled={ ! productsMap.size } displayTransform={ transformTokenIntoProductName } label={ __( - 'Pick some products', + 'Hand-picked Products', 'woo-gutenberg-products-block' ) } onChange={ onTokenChange } @@ -135,6 +141,7 @@ const HandPickedProductsControl = ( { : selectedProductIds || [] } __experimentalExpandOnFocus={ true } + __experimentalShowHowTo={ false } /> ); diff --git a/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts b/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts index 0fdbe242949..ea28c52dd9e 100644 --- a/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts +++ b/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts @@ -124,7 +124,7 @@ test.describe( 'Product Collection', () => { } ) => { await pageObject.addFilter( 'Show Hand-picked Products' ); - const filterName = 'Pick some products'; + const filterName = 'Hand-picked Products'; await pageObject.setFilterComboboxValue( filterName, [ 'Album' ] ); await expect( pageObject.products ).toHaveCount( 1 ); From afdcb150900832c0059969dfcebfb0dd86a4ed58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alba=20Rinc=C3=B3n?= Date: Mon, 27 Nov 2023 12:08:17 +0100 Subject: [PATCH 08/11] Convert to tsx and replace proptypes by ts definitions (#10471) * Convert to tsx and replace proptypes by ts definitions * Fix imports * Fix noReviewsPlaceholder type * Fix ts errors * Use createHigherOrderComponent in withReviews * Revert hoc change --------- Co-authored-by: Niels Lange --- .../js/blocks/reviews/all-reviews/block.tsx | 2 +- ...er-block.js => editor-container-block.tsx} | 16 +++----- .../reviews/reviews-by-category/block.tsx | 2 +- .../reviews/reviews-by-product/edit.tsx | 2 +- assets/js/blocks/reviews/types.ts | 41 +++++++++++++++++++ 5 files changed, 49 insertions(+), 14 deletions(-) rename assets/js/blocks/reviews/{editor-container-block.js => editor-container-block.tsx} (81%) create mode 100644 assets/js/blocks/reviews/types.ts diff --git a/assets/js/blocks/reviews/all-reviews/block.tsx b/assets/js/blocks/reviews/all-reviews/block.tsx index e7da183926c..50883160447 100644 --- a/assets/js/blocks/reviews/all-reviews/block.tsx +++ b/assets/js/blocks/reviews/all-reviews/block.tsx @@ -9,7 +9,7 @@ import { Icon, postComments } from '@wordpress/icons'; /** * Internal dependencies */ -import EditorContainerBlock from '../editor-container-block.js'; +import EditorContainerBlock from '../editor-container-block'; import NoReviewsPlaceholder from './no-reviews-placeholder'; import { getSharedReviewContentControls, diff --git a/assets/js/blocks/reviews/editor-container-block.js b/assets/js/blocks/reviews/editor-container-block.tsx similarity index 81% rename from assets/js/blocks/reviews/editor-container-block.js rename to assets/js/blocks/reviews/editor-container-block.tsx index 1d1c0c579b3..d8d824dd70f 100644 --- a/assets/js/blocks/reviews/editor-container-block.js +++ b/assets/js/blocks/reviews/editor-container-block.tsx @@ -2,10 +2,10 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; import { debounce } from '@woocommerce/base-utils'; import { Placeholder } from '@wordpress/components'; import { useBlockProps } from '@wordpress/block-editor'; +import { EditorContainerBlockProps } from '@woocommerce/blocks/reviews/types'; /** * Internal dependencies @@ -18,7 +18,7 @@ const EditorContainerBlock = ( { icon, name, noReviewsPlaceholder, -} ) => { +}: EditorContainerBlockProps ) => { const { categoryIds, productId, @@ -59,7 +59,9 @@ const EditorContainerBlock = ( { debounce( callback, 400 ) } + delayFunction={ ( callback: () => void ) => + debounce( callback, 400 ) + } noReviewsPlaceholder={ noReviewsPlaceholder } orderby={ orderby } order={ order } @@ -70,12 +72,4 @@ const EditorContainerBlock = ( { ); }; -EditorContainerBlock.propTypes = { - attributes: PropTypes.object.isRequired, - icon: PropTypes.node.isRequired, - name: PropTypes.string.isRequired, - noReviewsPlaceholder: PropTypes.elementType.isRequired, - className: PropTypes.string, -}; - export default EditorContainerBlock; diff --git a/assets/js/blocks/reviews/reviews-by-category/block.tsx b/assets/js/blocks/reviews/reviews-by-category/block.tsx index dc412191bea..5fe3ee92e8f 100644 --- a/assets/js/blocks/reviews/reviews-by-category/block.tsx +++ b/assets/js/blocks/reviews/reviews-by-category/block.tsx @@ -16,7 +16,7 @@ import { Icon, commentContent } from '@wordpress/icons'; /** * Internal dependencies */ -import EditorContainerBlock from '../editor-container-block.js'; +import EditorContainerBlock from '../editor-container-block'; import NoReviewsPlaceholder from './no-reviews-placeholder'; import { getBlockControls, diff --git a/assets/js/blocks/reviews/reviews-by-product/edit.tsx b/assets/js/blocks/reviews/reviews-by-product/edit.tsx index 3c9155a69a3..5c36c242e5e 100644 --- a/assets/js/blocks/reviews/reviews-by-product/edit.tsx +++ b/assets/js/blocks/reviews/reviews-by-product/edit.tsx @@ -16,7 +16,7 @@ import { commentContent, Icon } from '@wordpress/icons'; /** * Internal dependencies */ -import EditorContainerBlock from '../editor-container-block.js'; +import EditorContainerBlock from '../editor-container-block'; import NoReviewsPlaceholder from './no-reviews-placeholder.js'; import { getBlockControls, diff --git a/assets/js/blocks/reviews/types.ts b/assets/js/blocks/reviews/types.ts new file mode 100644 index 00000000000..2bef274784c --- /dev/null +++ b/assets/js/blocks/reviews/types.ts @@ -0,0 +1,41 @@ +export interface PreviewReviews { + id: number; + date_created: string; + formatted_date_created: string; + date_created_gmt: string; + product_id: number; + product_name: string; + product_permalink: string; + reviewer: string; + review: string; + reviewer_avatar_urls: { [ id: number ]: string }; + rating: number; + verified: boolean; +} + +export interface Attributes { + categoryIds?: number[]; + editMode?: boolean; + imageType?: string; + orderby?: string; + productId?: number; + reviewsOnLoadMore?: number; + reviewsOnPageLoad?: number; + showLoadMore?: boolean; + showOrderby?: boolean; + showProductName?: boolean; + showReviewDate?: boolean; + showReviewerName?: boolean; + showReviewImage?: boolean; + showReviewRating?: boolean; + showReviewContent?: boolean; + previewReviews?: PreviewReviews[]; +} + +export interface EditorContainerBlockProps { + attributes: Attributes; + icon: JSX.Element; + name: string; + noReviewsPlaceholder: React.ElementType; + className?: string; +} From 604f9705d19dfdc4e2efec566212c3c8a9eb355b Mon Sep 17 00:00:00 2001 From: Arsany Benyamine <83728833+arsanyjoseph@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:14:58 +0200 Subject: [PATCH 09/11] replace Stories root Dir to "External Components" (#11910) --- assets/js/base/components/button/stories/index.stories.tsx | 2 +- packages/components/form-step/stories/index.stories.tsx | 2 +- packages/components/spinner/stories/index.stories.tsx | 2 +- packages/components/textarea/stories/index.stories.tsx | 2 +- packages/components/title/stories/index.stories.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/js/base/components/button/stories/index.stories.tsx b/assets/js/base/components/button/stories/index.stories.tsx index f445dbdca86..e8ccf98f602 100644 --- a/assets/js/base/components/button/stories/index.stories.tsx +++ b/assets/js/base/components/button/stories/index.stories.tsx @@ -10,7 +10,7 @@ import Button, { ButtonProps } from '..'; const availableTypes = [ 'button', 'input', 'submit' ]; export default { - title: 'Base Components/Button', + title: 'External Components/Button', argTypes: { children: { control: 'text', diff --git a/packages/components/form-step/stories/index.stories.tsx b/packages/components/form-step/stories/index.stories.tsx index 895e6af8bda..e4f7b3a5d2f 100644 --- a/packages/components/form-step/stories/index.stories.tsx +++ b/packages/components/form-step/stories/index.stories.tsx @@ -12,7 +12,7 @@ import FormStep, { type FormStepProps } from '..'; import '../style.scss'; export default { - title: 'Checkout Components/FormStep', + title: 'External Components/FormStep', component: FormStep, argTypes: { className: { diff --git a/packages/components/spinner/stories/index.stories.tsx b/packages/components/spinner/stories/index.stories.tsx index a6aee769280..b303eb051d6 100644 --- a/packages/components/spinner/stories/index.stories.tsx +++ b/packages/components/spinner/stories/index.stories.tsx @@ -9,7 +9,7 @@ import type { Meta, StoryFn } from '@storybook/react'; import Spinner from '..'; export default { - title: 'Block Components/Spinner', + title: 'External Components/Spinner', component: Spinner, } as Meta; diff --git a/packages/components/textarea/stories/index.stories.tsx b/packages/components/textarea/stories/index.stories.tsx index c6c2adfe56a..d83c5ca7e9b 100644 --- a/packages/components/textarea/stories/index.stories.tsx +++ b/packages/components/textarea/stories/index.stories.tsx @@ -11,7 +11,7 @@ import Textarea, { type TextareaProps } from '..'; import '../style.scss'; export default { - title: 'Checkout Components/Textarea', + title: 'External Components/Textarea', component: Textarea, argTypes: { className: { diff --git a/packages/components/title/stories/index.stories.tsx b/packages/components/title/stories/index.stories.tsx index 5627e8b7363..57a2bcc1e6a 100644 --- a/packages/components/title/stories/index.stories.tsx +++ b/packages/components/title/stories/index.stories.tsx @@ -10,7 +10,7 @@ import Title, { type TitleProps } from '..'; import '../style.scss'; export default { - title: 'Block Components/Title', + title: 'External Components/Title', component: Title, argTypes: { className: { From 1717221b0cdf7f1f3f2e49fc47e69fb6cee5a6ef Mon Sep 17 00:00:00 2001 From: Agung Sundoro Date: Mon, 27 Nov 2023 20:07:18 +0700 Subject: [PATCH 10/11] fix: store notices always shows as an error type #11768 (#11932) --- packages/components/store-notices-container/store-notices.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/store-notices-container/store-notices.tsx b/packages/components/store-notices-container/store-notices.tsx index 8680fd7ae95..5b2fd856ad8 100644 --- a/packages/components/store-notices-container/store-notices.tsx +++ b/packages/components/store-notices-container/store-notices.tsx @@ -127,7 +127,7 @@ const StoreNotices = ( { key: string; } = { key: `store-notice-${ status }`, - status: 'error', + status, onRemove: () => { noticeGroup.forEach( ( notice ) => { removeNotice( notice.id, notice.context ); From 88761df756e8dd6f5f18df714a6923bb0a05e31a Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 27 Nov 2023 13:43:42 +0000 Subject: [PATCH 11/11] Switch to NoticeBanner component inside Store Notices Block placeholder (#11920) --- assets/js/blocks/store-notices/edit.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/blocks/store-notices/edit.tsx b/assets/js/blocks/store-notices/edit.tsx index d991b627ba5..da261306d7b 100644 --- a/assets/js/blocks/store-notices/edit.tsx +++ b/assets/js/blocks/store-notices/edit.tsx @@ -3,7 +3,7 @@ */ import { useBlockProps } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; -import { Notice } from '@wordpress/components'; +import NoticeBanner from '@woocommerce/base-components/notice-banner'; /** * Internal dependencies @@ -17,12 +17,12 @@ const Edit = (): JSX.Element => { return (
- + { __( 'Notices added by WooCommerce or extensions will show up here.', 'woo-gutenberg-products-block' ) } - +
); };