From 0696a649b9254a48f24a50e40358d2a4663eb752 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 12 Jun 2023 01:44:01 +0530 Subject: [PATCH 1/4] migrate to typescript --- .../all-products/{edit.js => edit.tsx} | 64 +++++++++++-------- .../js/previews/{products.js => products.ts} | 9 ++- 2 files changed, 46 insertions(+), 27 deletions(-) rename assets/js/blocks/products/all-products/{edit.js => edit.tsx} (86%) rename assets/js/previews/{products.js => products.ts} (89%) diff --git a/assets/js/blocks/products/all-products/edit.js b/assets/js/blocks/products/all-products/edit.tsx similarity index 86% rename from assets/js/blocks/products/all-products/edit.js rename to assets/js/blocks/products/all-products/edit.tsx index e7ba049b5e5..6566dc82899 100644 --- a/assets/js/blocks/products/all-products/edit.js +++ b/assets/js/blocks/products/all-products/edit.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { createBlock } from '@wordpress/blocks'; +import { createBlock, BlockInstance } from '@wordpress/blocks'; import { BlockControls, InnerBlocks, @@ -20,7 +20,6 @@ import { } from '@wordpress/components'; import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; -import PropTypes from 'prop-types'; import { Icon, grid } from '@wordpress/icons'; import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; import { @@ -48,25 +47,37 @@ import { getSharedContentControls, getSharedListControls } from '../edit'; import Block from './block'; import './editor.scss'; +type EditorAttributes = { + columns: number; + rows: number; + alignButtons: boolean; + contentVisibility: object; + orderby: string; + layoutConfig: Array< number >; + isPreview: boolean; +}; + +interface EditorProps { + block: BlockInstance; + attributes: EditorAttributes; + debouncedSpeak: ( label: string ) => void; + setAttributes: ( attributes: Record< string, unknown > ) => void; + replaceInnerBlocks: ( + rootClientId: string, + blocks: BlockInstance[], + updateSelection?: boolean + ) => void; +} + +interface EditorState { + isEditing: boolean; + innerBlocks: BlockInstance[]; +} + /** * Component to handle edit mode of "All Products". */ -class Editor extends Component { - static propTypes = { - /** - * The attributes for this block. - */ - attributes: PropTypes.object.isRequired, - /** - * A callback to update attributes. - */ - setAttributes: PropTypes.func.isRequired, - /** - * From withSpokenMessages. - */ - debouncedSpeak: PropTypes.func.isRequired, - }; - +class Editor extends Component< EditorProps, EditorState > { state = { isEditing: false, innerBlocks: [], @@ -74,20 +85,20 @@ class Editor extends Component { blockMap = getBlockMap( 'woocommerce/all-products' ); - componentDidMount = () => { + componentDidMount = (): void => { const { block } = this.props; this.setState( { innerBlocks: block.innerBlocks } ); }; - getTitle = () => { + getTitle = (): string => { return __( 'All Products', 'woo-gutenberg-products-block' ); }; - getIcon = () => { + getIcon = (): JSX.Element => { return ; }; - togglePreview = () => { + togglePreview = (): void => { const { debouncedSpeak } = this.props; this.setState( { isEditing: ! this.state.isEditing } ); @@ -102,7 +113,7 @@ class Editor extends Component { } }; - getInspectorControls = () => { + getInspectorControls = (): JSX.Element => { const { attributes, setAttributes } = this.props; const { columns, rows, alignButtons } = attributes; @@ -139,7 +150,7 @@ class Editor extends Component { ); }; - getBlockControls = () => { + getBlockControls = (): JSX.Element => { const { isEditing } = this.state; return ( @@ -180,7 +191,7 @@ class Editor extends Component { const onReset = () => { const { block, replaceInnerBlocks } = this.props; - const newBlocks = []; + const newBlocks: BlockInstance[] = []; DEFAULT_PRODUCT_LIST_LAYOUT.map( ( [ name, attributes ] ) => { newBlocks.push( createBlock( name, attributes ) ); return true; @@ -193,6 +204,7 @@ class Editor extends Component { template: this.props.attributes.layoutConfig, templateLock: false, allowedBlocks: Object.keys( this.blockMap ), + renderAppender: true, }; if ( this.props.attributes.layoutConfig.length !== 0 ) { @@ -222,6 +234,7 @@ class Editor extends Component { + { /* @ts-expect-error: `InnerBlocks` is a component that is typed in WordPress core*/ } @@ -276,6 +289,7 @@ class Editor extends Component { return ( + { /* @ts-expect-error: `Block` is a component that is typed in WordPress core*/ } ); diff --git a/assets/js/previews/products.js b/assets/js/previews/products.ts similarity index 89% rename from assets/js/previews/products.js rename to assets/js/previews/products.ts index 441cf28af40..75ba459a5df 100644 --- a/assets/js/previews/products.js +++ b/assets/js/previews/products.ts @@ -3,13 +3,14 @@ */ import { __ } from '@wordpress/i18n'; import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings'; +import { ProductResponseItem } from '@woocommerce/types'; const shortDescription = __( 'Fly your WordPress banner with this beauty! Deck out your office space or add it to your kids walls. This banner will spruce up any space it’s hung!', 'woo-gutenberg-products-block' ); -export const previewProducts = [ +export const previewProducts: Array< ProductResponseItem > = [ { id: 1, name: 'WordPress Pennant', @@ -33,7 +34,7 @@ export const previewProducts = [ sizes: '', }, ], - average_rating: 5, + average_rating: '5', categories: [ { id: 1, @@ -58,6 +59,10 @@ export const previewProducts = [ add_to_cart: { text: __( 'Add to cart', 'woo-gutenberg-products-block' ), description: __( 'Add to cart', 'woo-gutenberg-products-block' ), + url: '', + minimum: 1, + maximum: 99, + multiple_of: 1, }, has_options: false, is_purchasable: true, From ed48fc3b5c02d1d9163c24087c4e179aff3cbda1 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 12 Jun 2023 23:06:43 +0530 Subject: [PATCH 2/4] fix typescript errors --- assets/js/blocks/products/all-products/edit.tsx | 3 ++- assets/js/blocks/products/{base-utils.js => base-utils.ts} | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) rename assets/js/blocks/products/{base-utils.js => base-utils.ts} (88%) diff --git a/assets/js/blocks/products/all-products/edit.tsx b/assets/js/blocks/products/all-products/edit.tsx index 6566dc82899..cce7e399606 100644 --- a/assets/js/blocks/products/all-products/edit.tsx +++ b/assets/js/blocks/products/all-products/edit.tsx @@ -53,7 +53,7 @@ type EditorAttributes = { alignButtons: boolean; contentVisibility: object; orderby: string; - layoutConfig: Array< number >; + layoutConfig: Array< object >; isPreview: boolean; }; @@ -233,6 +233,7 @@ class Editor extends Component< EditorProps, EditorState > {
  • { /* @ts-expect-error: `InnerBlocks` is a component that is typed in WordPress core*/ } diff --git a/assets/js/blocks/products/base-utils.js b/assets/js/blocks/products/base-utils.ts similarity index 88% rename from assets/js/blocks/products/base-utils.js rename to assets/js/blocks/products/base-utils.ts index 236ae3e838f..53b36d6bde9 100644 --- a/assets/js/blocks/products/base-utils.js +++ b/assets/js/blocks/products/base-utils.ts @@ -2,6 +2,7 @@ * External dependencies */ import classnames from 'classnames'; +import { BlockInstance } from '@wordpress/blocks'; /** * Internal dependencies @@ -12,7 +13,7 @@ import { ImageSizing } from '../../atomic/blocks/product-elements/image/types'; /** * The default layout built from the default template. */ -export const DEFAULT_PRODUCT_LIST_LAYOUT = [ +export const DEFAULT_PRODUCT_LIST_LAYOUT: [ string, object? ][] = [ [ 'woocommerce/product-image', { imageSizing: ImageSizing.THUMBNAIL } ], [ 'woocommerce/product-title' ], [ 'woocommerce/product-price' ], @@ -25,7 +26,9 @@ export const DEFAULT_PRODUCT_LIST_LAYOUT = [ * * @param {Object[]} innerBlocks Inner block components. */ -export const getProductLayoutConfig = ( innerBlocks ) => { +export const getProductLayoutConfig = ( + innerBlocks: BlockInstance[] +): [ string, object? ][] => { if ( ! innerBlocks || innerBlocks.length === 0 ) { return []; } From 742ec904ff778e73a63104cee2dd5b57f717b1ea Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sat, 17 Jun 2023 00:43:58 +0530 Subject: [PATCH 3/4] move from class to functional component --- .../js/blocks/products/all-products/edit.tsx | 202 ++++++++---------- .../js/blocks/products/all-products/index.js | 8 +- .../js/blocks/products/all-products/save.js | 2 +- 3 files changed, 90 insertions(+), 122 deletions(-) diff --git a/assets/js/blocks/products/all-products/edit.tsx b/assets/js/blocks/products/all-products/edit.tsx index cce7e399606..adbafb68035 100644 --- a/assets/js/blocks/products/all-products/edit.tsx +++ b/assets/js/blocks/products/all-products/edit.tsx @@ -8,18 +8,17 @@ import { InnerBlocks, InspectorControls, } from '@wordpress/block-editor'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { PanelBody, - withSpokenMessages, Placeholder, Button, ToolbarGroup, Disabled, Tip, } from '@wordpress/components'; -import { Component } from '@wordpress/element'; -import { compose } from '@wordpress/compose'; +import { useState, useEffect } from '@wordpress/element'; +import { useDebounce } from '@wordpress/compose'; import { Icon, grid } from '@wordpress/icons'; import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; import { @@ -30,6 +29,7 @@ import { getBlockMap } from '@woocommerce/atomic-utils'; import { previewProducts } from '@woocommerce/resource-previews'; import { getSetting } from '@woocommerce/settings'; import { blocksConfig } from '@woocommerce/block-settings'; +import { speak } from '@wordpress/a11y'; /** * Internal dependencies @@ -47,63 +47,58 @@ import { getSharedContentControls, getSharedListControls } from '../edit'; import Block from './block'; import './editor.scss'; -type EditorAttributes = { +type Attributes = { columns: number; rows: number; alignButtons: boolean; - contentVisibility: object; - orderby: string; - layoutConfig: Array< object >; - isPreview: boolean; + layoutConfig: [ string, object? ][]; }; -interface EditorProps { - block: BlockInstance; - attributes: EditorAttributes; - debouncedSpeak: ( label: string ) => void; +type Props = { + contentVisibility: number; + orderby: number; + isPreview: number; + clientId: string; + attributes: Attributes; setAttributes: ( attributes: Record< string, unknown > ) => void; - replaceInnerBlocks: ( - rootClientId: string, - blocks: BlockInstance[], - updateSelection?: boolean - ) => void; -} +}; -interface EditorState { - isEditing: boolean; - innerBlocks: BlockInstance[]; -} +export default function Edit( props: Props ): JSX.Element { + const [ isEditing, setIsEditing ] = useState< boolean >( false ); + const [ , setInnerBlocks ] = useState< BlockInstance[] | boolean >( false ); + const blockMap = getBlockMap( 'woocommerce/all-products' ); -/** - * Component to handle edit mode of "All Products". - */ -class Editor extends Component< EditorProps, EditorState > { - state = { - isEditing: false, - innerBlocks: [], - }; + const { clientId, attributes, setAttributes } = props; - blockMap = getBlockMap( 'woocommerce/all-products' ); + const { columns, rows, alignButtons, layoutConfig } = attributes; - componentDidMount = (): void => { - const { block } = this.props; - this.setState( { innerBlocks: block.innerBlocks } ); - }; + const { innerBlocks } = useSelect( ( select ) => { + const { getBlock } = select( 'core/block-editor' ); + const block = getBlock( clientId ); + return { + innerBlocks: block ? block.innerBlocks : [], + }; + } ); - getTitle = (): string => { + useEffect( () => { + setInnerBlocks( innerBlocks ); + }, [] ); + + const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); + const debouncedSpeak = useDebounce( speak ); + + const getTitle = (): string => { return __( 'All Products', 'woo-gutenberg-products-block' ); }; - getIcon = (): JSX.Element => { + const getIcon = (): JSX.Element => { return ; }; - togglePreview = (): void => { - const { debouncedSpeak } = this.props; - - this.setState( { isEditing: ! this.state.isEditing } ); + const togglePreview = (): void => { + setIsEditing( ! isEditing ); - if ( ! this.state.isEditing ) { + if ( ! isEditing ) { debouncedSpeak( __( 'Showing All Products block preview.', @@ -113,10 +108,7 @@ class Editor extends Component< EditorProps, EditorState > { } }; - getInspectorControls = (): JSX.Element => { - const { attributes, setAttributes } = this.props; - const { columns, rows, alignButtons } = attributes; - + const getInspectorControls = (): JSX.Element => { return ( { rows={ rows } alignButtons={ alignButtons } setAttributes={ setAttributes } - minColumns={ getSetting( 'min_columns', 1 ) } - maxColumns={ getSetting( 'max_columns', 6 ) } - minRows={ getSetting( 'min_rows', 1 ) } - maxRows={ getSetting( 'max_rows', 6 ) } + minColumns={ getSetting( 'min_columns', 1 ) as number } + maxColumns={ getSetting( 'max_columns', 6 ) as number } + minRows={ getSetting( 'min_rows', 1 ) as number } + maxRows={ getSetting( 'max_rows', 6 ) as number } /> { ); }; - getBlockControls = (): JSX.Element => { - const { isEditing } = this.state; - + const getBlockControls = (): JSX.Element => { return ( { 'Edit the layout of each product', 'woo-gutenberg-products-block' ), - onClick: () => this.togglePreview(), + onClick: () => togglePreview(), isActive: isEditing, }, ] } @@ -172,47 +162,47 @@ class Editor extends Component< EditorProps, EditorState > { ); }; - renderEditMode = () => { + const renderEditMode = () => { const onDone = () => { - const { block, setAttributes } = this.props; setAttributes( { - layoutConfig: getProductLayoutConfig( block.innerBlocks ), + layoutConfig: getProductLayoutConfig( innerBlocks ), } ); - this.setState( { innerBlocks: block.innerBlocks } ); - this.togglePreview(); + setInnerBlocks( innerBlocks ); + togglePreview(); }; const onCancel = () => { - const { block, replaceInnerBlocks } = this.props; - const { innerBlocks } = this.state; - replaceInnerBlocks( block.clientId, innerBlocks, false ); - this.togglePreview(); + replaceInnerBlocks( clientId, innerBlocks, false ); + togglePreview(); }; const onReset = () => { - const { block, replaceInnerBlocks } = this.props; const newBlocks: BlockInstance[] = []; - DEFAULT_PRODUCT_LIST_LAYOUT.map( ( [ name, attributes ] ) => { - newBlocks.push( createBlock( name, attributes ) ); + DEFAULT_PRODUCT_LIST_LAYOUT.map( ( [ name, blockAttributes ] ) => { + newBlocks.push( createBlock( name, blockAttributes ) ); return true; } ); - replaceInnerBlocks( block.clientId, newBlocks, false ); - this.setState( { innerBlocks: block.innerBlocks } ); + replaceInnerBlocks( clientId, newBlocks, false ); + setInnerBlocks( innerBlocks ); }; - const InnerBlockProps = { - template: this.props.attributes.layoutConfig, + const InnerBlockProps: { + template: [ string, object? ][]; + templateLock: boolean; + allowedBlocks: Array< string >; + renderAppender?: undefined | boolean; + } = { + template: layoutConfig, templateLock: false, - allowedBlocks: Object.keys( this.blockMap ), - renderAppender: true, + allowedBlocks: Object.keys( blockMap ), }; - if ( this.props.attributes.layoutConfig.length !== 0 ) { + if ( layoutConfig.length !== 0 ) { InnerBlockProps.renderAppender = false; } return ( - + { __( 'Display all products from your store as a grid.', 'woo-gutenberg-products-block' @@ -277,12 +267,10 @@ class Editor extends Component< EditorProps, EditorState > { ); }; - renderViewMode = () => { - const { attributes } = this.props; - const { layoutConfig } = attributes; + const renderViewMode = () => { const hasContent = layoutConfig && layoutConfig.length !== 0; - const blockTitle = this.getTitle(); - const blockIcon = this.getIcon(); + const blockTitle = getTitle(); + const blockIcon = getIcon(); if ( ! hasContent ) { return renderHiddenContentPlaceholder( blockTitle, blockIcon ); @@ -296,43 +284,23 @@ class Editor extends Component< EditorProps, EditorState > { ); }; - render = () => { - const { attributes } = this.props; - const { isEditing } = this.state; - const blockTitle = this.getTitle(); - const blockIcon = this.getIcon(); + const blockTitle = getTitle(); + const blockIcon = getIcon(); - if ( blocksConfig.productCount === 0 ) { - return renderNoProductsPlaceholder( blockTitle, blockIcon ); - } + if ( blocksConfig.productCount === 0 ) { + return renderNoProductsPlaceholder( blockTitle, blockIcon ); + } - return ( -
    - { this.getBlockControls() } - { this.getInspectorControls() } - { isEditing ? this.renderEditMode() : this.renderViewMode() } -
    - ); - }; + return ( +
    + { getBlockControls() } + { getInspectorControls() } + { isEditing ? renderEditMode() : renderViewMode() } +
    + ); } - -export default compose( - withSpokenMessages, - withSelect( ( select, { clientId } ) => { - const { getBlock } = select( 'core/block-editor' ); - return { - block: getBlock( clientId ), - }; - } ), - withDispatch( ( dispatch ) => { - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); - return { - replaceInnerBlocks, - }; - } ) -)( Editor ); diff --git a/assets/js/blocks/products/all-products/index.js b/assets/js/blocks/products/all-products/index.js index 659c21724a9..fdf518361e5 100644 --- a/assets/js/blocks/products/all-products/index.js +++ b/assets/js/blocks/products/all-products/index.js @@ -10,8 +10,8 @@ import '@woocommerce/atomic-blocks'; */ import metadata from './block.json'; import deprecated from './deprecated'; -import edit from './edit'; -import save from './save'; +import Edit from './edit'; +import Save from './save'; import defaults from './defaults'; const { name } = metadata; @@ -26,9 +26,9 @@ const settings = { /> ), }, - edit, + edit: Edit, // Save the props to post content. - save, + save: Save, deprecated, defaults, }; diff --git a/assets/js/blocks/products/all-products/save.js b/assets/js/blocks/products/all-products/save.js index 108ae322d63..e9683b910ae 100644 --- a/assets/js/blocks/products/all-products/save.js +++ b/assets/js/blocks/products/all-products/save.js @@ -8,7 +8,7 @@ import { InnerBlocks } from '@wordpress/block-editor'; */ import { getBlockClassName } from '../utils.js'; -export default function save( { attributes } ) { +export default function Save( { attributes } ) { const dataAttributes = {}; Object.keys( attributes ) .sort() From 2ecc0328ff42de4daa906a2f3ac1d36b9866c03b Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sun, 25 Jun 2023 20:43:56 +0530 Subject: [PATCH 4/4] PR review changes --- assets/js/blocks/products/all-products/edit.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/assets/js/blocks/products/all-products/edit.tsx b/assets/js/blocks/products/all-products/edit.tsx index adbafb68035..0db6f3b46e8 100644 --- a/assets/js/blocks/products/all-products/edit.tsx +++ b/assets/js/blocks/products/all-products/edit.tsx @@ -8,7 +8,7 @@ import { InnerBlocks, InspectorControls, } from '@wordpress/block-editor'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; import { PanelBody, Placeholder, @@ -65,21 +65,13 @@ type Props = { export default function Edit( props: Props ): JSX.Element { const [ isEditing, setIsEditing ] = useState< boolean >( false ); - const [ , setInnerBlocks ] = useState< BlockInstance[] | boolean >( false ); + const [ innerBlocks, setInnerBlocks ] = useState< BlockInstance[] >( [] ); const blockMap = getBlockMap( 'woocommerce/all-products' ); const { clientId, attributes, setAttributes } = props; const { columns, rows, alignButtons, layoutConfig } = attributes; - const { innerBlocks } = useSelect( ( select ) => { - const { getBlock } = select( 'core/block-editor' ); - const block = getBlock( clientId ); - return { - innerBlocks: block ? block.innerBlocks : [], - }; - } ); - useEffect( () => { setInnerBlocks( innerBlocks ); }, [] );