Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Product Collection: Implement Preview Mode #46369

Merged
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
681110f
POC: Preview Mode using HOC
imanish003 Mar 19, 2024
d139b22
Add explanation as comments
imanish003 Mar 19, 2024
b61b00f
Refactor preview state handling and collection registration
imanish003 Mar 21, 2024
39e0128
Rename file
imanish003 Mar 21, 2024
95fbfe7
Minor improvements
imanish003 Mar 21, 2024
109c961
Don't pass previewState to handlePreviewState
imanish003 Mar 22, 2024
9a3ed04
Add comment
imanish003 Mar 22, 2024
0055a48
Use JS closure to inject handlePreviewState
imanish003 Mar 22, 2024
7950dfd
Refactor preview state management into custom hook
imanish003 Mar 22, 2024
b268b79
Resolve conflicts
imanish003 Apr 1, 2024
ce7b35f
Replace useEffect with useLayoutEffect
imanish003 Apr 1, 2024
2370b1e
Add cleanup function in handlePreviewState function
imanish003 Apr 1, 2024
29b1e15
Fetching random products in Preview mode
imanish003 Apr 2, 2024
3a66aa2
Allow collection to set initial preview state
imanish003 Apr 2, 2024
10c1a75
Resolve conflicts
imanish003 Apr 3, 2024
76c4284
Pass location & all attributes to handlePreviewState function
imanish003 Apr 3, 2024
0caf11d
Handling collection specific query for preview mode
imanish003 Apr 3, 2024
aee02e4
Always set initialPreviewState on load
imanish003 Apr 3, 2024
c8d5ef8
Refine preview state handling
imanish003 Apr 3, 2024
9992019
Rename "initialState" to "initialPreviewState"
imanish003 Apr 8, 2024
f17a694
Fix: Correct merging of newPreviewState into previewState attribute
imanish003 Apr 8, 2024
1ccac85
Merge branch 'try/product-collection-is-preview-POC' of https://githu…
imanish003 Apr 9, 2024
4aa2432
Initial refactor POC code to productionize it
imanish003 Apr 9, 2024
423d710
Move `useSetPreviewState` to Utils
imanish003 Apr 10, 2024
d18b015
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce in…
imanish003 Apr 23, 2024
b463b3b
Implement preview mode for Generic archive templates
imanish003 Apr 24, 2024
30bede2
Remove preview mode from Featured and On sale collection
imanish003 Apr 24, 2024
6d6eae2
Remove preview query modfication for On Sale collection
imanish003 Apr 24, 2024
7ec76f2
Add changefile(s) from automation for the following project(s): wooco…
invalid-email-address Apr 25, 2024
d4d0e08
Fix: hide/show preview label based on value of "inherit"
imanish003 Apr 25, 2024
95f5778
Merge branch 'add/46368-product-collection-productionize-the-preview-…
imanish003 Apr 25, 2024
0e702aa
Minor improvements
imanish003 Apr 25, 2024
b84c73b
Add changefile(s) from automation for the following project(s): wooco…
invalid-email-address Apr 25, 2024
5741fd9
Add changefile(s) from automation for the following project(s): wooco…
invalid-email-address Apr 25, 2024
76b310f
Refactor: Simplify SetPreviewState type definition in types.ts
imanish003 May 7, 2024
01b36f2
Update import syntax for ElementType in register-product-collection.tsx
imanish003 May 7, 2024
5cfb2ac
Refactor: Update TypeScript usage in Product Collection
imanish003 May 7, 2024
7802a6d
Refactor: Update dependencies of useSetPreviewState hook in utils.tsx
imanish003 May 7, 2024
36de12b
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce in…
imanish003 May 7, 2024
adacede
Refactor preview button CSS and conditional rendering
imanish003 May 8, 2024
4de5a96
Enhance: Update preview button visibility logic in ProductCollectionC…
imanish003 May 8, 2024
ab29e68
use __private prefix with attribute name
imanish003 May 8, 2024
4bcec0b
Add E2E tests for Preview Mode
imanish003 May 8, 2024
f8ccbcc
Add setPreviewState to dependencies
imanish003 May 10, 2024
8c9b9d7
Add data-test-id to Preview button and update e2e locator
imanish003 May 10, 2024
790f664
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce in…
imanish003 May 10, 2024
856f791
Enhance: Localize preview message in useSetPreviewState hook
imanish003 May 10, 2024
6c6181f
Don't show shadow & outline on focus
imanish003 May 13, 2024
d530f00
Make preview button font same as Admin
imanish003 May 14, 2024
6b8ad73
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce in…
imanish003 May 14, 2024
13af5b3
Fix SCSS lint errors
imanish003 May 14, 2024
f93ebcd
Merge branch 'trunk' of https://github.com/woocommerce/woocommerce in…
imanish003 May 15, 2024
b08a481
Add missing await keyword
imanish003 May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,18 @@
"forcePageReload": {
"type": "boolean",
"default": false
},
"__privatePreviewState": {
"type": "object"
}
},
"providesContext": {
"queryId": "queryId",
"query": "query",
"displayLayout": "displayLayout",
"queryContextIncludes": "queryContextIncludes",
"collection": "collection"
"collection": "collection",
"__privateProductCollectionPreviewState": "__privatePreviewState"
},
"usesContext": [ "templateSlug" ],
"supports": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
// @ts-expect-error Type definition is missing
store as blocksStore,
type BlockVariation,
registerBlockVariation,
BlockAttributes,
} from '@wordpress/blocks';

Expand All @@ -21,6 +20,7 @@ import topRated from './top-rated';
import bestSellers from './best-sellers';
import onSale from './on-sale';
import featured from './featured';
import registerProductCollection from './register-product-collection';

const collections: BlockVariation[] = [
productCollection,
Expand All @@ -40,7 +40,7 @@ export const registerCollections = () => {
return blockAttrs.collection === variationAttributes.collection;
};

registerBlockVariation( blockJson.name, {
registerProductCollection( {
isActive,
...collection,
} );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* External dependencies
*/
import { BlockVariation, registerBlockVariation } from '@wordpress/blocks';
import { addFilter } from '@wordpress/hooks';
import { EditorBlock } from '@woocommerce/types';
import type { ElementType } from '@wordpress/element';
import type { BlockEditProps } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import blockJson from '../block.json';
import {
SetPreviewState,
PreviewState,
ProductCollectionAttributes,
} from '../types';

export interface ProductCollectionConfig extends BlockVariation {
preview?: {
setPreviewState?: SetPreviewState;
initialPreviewState?: PreviewState;
};
}

/**
* Register a new collection for the Product Collection block.
*
* @param {ProductCollectionConfig} blockVariationArgs The configuration of new collection.
*/
const registerProductCollection = ( {
imanish003 marked this conversation as resolved.
Show resolved Hide resolved
preview: { setPreviewState, initialPreviewState } = {},
...blockVariationArgs
}: ProductCollectionConfig ) => {
/**
* If setPreviewState or initialPreviewState is provided, inject the setPreviewState & initialPreviewState props.
* This is useful for handling preview mode in the editor.
*/
if ( setPreviewState || initialPreviewState ) {
const withSetPreviewState =
< T extends EditorBlock< T > >( BlockEdit: ElementType ) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is BlockEdit supposed to be the edit component? ElementType would be too broad for that.

It would be more something like ComponentType.

The last defined types in Gberg def typed definitions: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/wordpress__blocks/index.d.ts#L180

(afaict latest blocks doesnt ship with types yet? shrug)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I spent some time trying to use ComponentType<BlockEditProps<T>> | undefined but it is resulting into more TS errors. I am getting following error, do you have any idea why I am getting this error? 🤔
image

If it helps, I tried to use ComponentType from `'@wordpress/element' as we aren't supposed to import directly from "react", right?

(afaict latest blocks doesnt ship with types yet? shrug)

Do you mean, woo blocks doesn't ship with types? 🤷🏻‍♂️

Copy link
Contributor

Choose a reason for hiding this comment

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

If it helps, I tried to use ComponentType from `'@wordpress/element' as we aren't supposed to import directly from "react", right?

as i mentioned in another comment, you absolutely can import types from react. types are erased at compilation. For some reason wp element is erasing the generics of the ComponentType so the fix here would indeed be to import from react (I tested and it works well).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am getting following error when I try importing from react:
image

I can ignore this lint error by adding following line:

// eslint-disable-next-line @typescript-eslint/no-restricted-imports, @woocommerce/dependency-group

What do you think? Is it worth it to import "ComponentType" from "react" and then disable eslint for import statement?

( props: BlockEditProps< ProductCollectionAttributes > ) => {
// If collection name does not match, return the original BlockEdit component.
if ( props.attributes.collection !== blockVariationArgs.name ) {
return <BlockEdit { ...props } />;
}

// Otherwise, inject the setPreviewState & initialPreviewState props.
return (
<BlockEdit
{ ...props }
preview={ {
setPreviewState,
initialPreviewState,
} }
/>
);
};
addFilter(
'editor.BlockEdit',
blockVariationArgs.name,
withSetPreviewState
);
}

registerBlockVariation( blockJson.name, {
...blockVariationArgs,
} );
};

export default registerProductCollection;
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ export const DEFAULT_QUERY: ProductCollectionQuery = {
priceRange: undefined,
};

export const DEFAULT_ATTRIBUTES: Partial< ProductCollectionAttributes > = {
export const DEFAULT_ATTRIBUTES: Pick<
ProductCollectionAttributes,
| 'query'
| 'tagName'
| 'displayLayout'
| 'queryContextIncludes'
| 'forcePageReload'
> = {
query: DEFAULT_QUERY,
tagName: 'div',
displayLayout: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
$max-columns: 3;
$min-button-width: 250px;
$gap-count: calc(#{ $max-columns } - 1);
$total-gap-width: calc(#{ $gap-count } * #{ $gap-small });
$max-button-width: calc((100% - #{ $total-gap-width }) / #{ $max-columns });
$gap-count: calc(#{$max-columns} - 1);
$total-gap-width: calc(#{$gap-count} * #{$gap-small});
$max-button-width: calc((100% - #{$total-gap-width}) / #{$max-columns});

.wc-block-editor-product-collection-inspector-toolspanel__filters {
.wc-block-editor-product-collection-inspector__taxonomy-control:not(:last-child) {
Expand Down Expand Up @@ -35,7 +35,7 @@ $max-button-width: calc((100% - #{ $total-gap-width }) / #{ $max-columns });

.wc-blocks-product-collection__collections-section {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(max(#{ $min-button-width }, #{ $max-button-width }), 1fr));
grid-template-columns: repeat(auto-fill, minmax(max(#{$min-button-width}, #{$max-button-width}), 1fr));
grid-auto-rows: 1fr;
grid-gap: $gap-small;
margin: $gap-large auto;
Expand Down Expand Up @@ -102,3 +102,17 @@ $max-button-width: calc((100% - #{ $total-gap-width }) / #{ $max-columns });
pointer-events: none;
}
}

// Preview mode
.wc-block-product-collection__preview-button {
position: absolute;
top: 0;
right: 0;
transform: translateY(-100%);
font-family: var(--wp--preset--font-family--system-sans-serif);

&.is-primary:focus:not(:disabled) {
outline: none;
box-shadow: none;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@
* External dependencies
*/
import { store as blockEditorStore } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import type { ProductCollectionAttributes } from '../types';
import type { ProductCollectionEditComponentProps } from '../types';
import ProductCollectionPlaceholder from './product-collection-placeholder';
import ProductCollectionContent from './product-collection-content';
import CollectionSelectionModal from './collection-selection-modal';
import './editor.scss';

const Edit = ( props: BlockEditProps< ProductCollectionAttributes > ) => {
const Edit = ( props: ProductCollectionEditComponentProps ) => {
const { clientId, attributes } = props;

const [ isSelectionModalOpen, setIsSelectionModalOpen ] = useState( false );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ForcePageReloadControl from './force-page-reload-control';
import type { ProductCollectionEditComponentProps } from '../../types';

export default function ProductCollectionAdvancedInspectorControls(
props: ProductCollectionEditComponentProps
props: Omit< ProductCollectionEditComponentProps, 'preview' >
) {
const { clientId, attributes, setAttributes } = props;
const { forcePageReload } = attributes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import {
store as blockEditorStore,
} from '@wordpress/block-editor';
import { useInstanceId } from '@wordpress/compose';
import { useEffect } from '@wordpress/element';
import { useEffect, useRef } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useGetLocation } from '@woocommerce/blocks/product-template/utils';

/**
* Internal dependencies
Expand All @@ -19,15 +21,28 @@ import type {
ProductCollectionEditComponentProps,
} from '../types';
import { DEFAULT_ATTRIBUTES, INNER_BLOCKS_TEMPLATE } from '../constants';
import { getDefaultValueOfInheritQueryFromTemplate } from '../utils';
import {
getDefaultValueOfInheritQueryFromTemplate,
useSetPreviewState,
} from '../utils';
import InspectorControls from './inspector-controls';
import InspectorAdvancedControls from './inspector-advanced-controls';
import ToolbarControls from './toolbar-controls';

const ProductCollectionContent = (
props: ProductCollectionEditComponentProps
) => {
const ProductCollectionContent = ( {
preview: { setPreviewState, initialPreviewState } = {},
...props
}: ProductCollectionEditComponentProps ) => {
const isInitialAttributesSet = useRef( false );
const { clientId, attributes, setAttributes } = props;
const location = useGetLocation( props.context, props.clientId );

useSetPreviewState( {
setPreviewState,
setAttributes,
location,
attributes,
} );

const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps( blockProps, {
Expand Down Expand Up @@ -75,13 +90,38 @@ const ProductCollectionContent = (
},
...( attributes as Partial< ProductCollectionAttributes > ),
queryId,
// If initialPreviewState is provided, set it as previewState.
...( !! attributes.collection && {
__privatePreviewState: initialPreviewState,
} ),
} );

isInitialAttributesSet.current = true;
},
// This hook is only needed on initialization and sets default attributes.
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);

const isSelectedOrInnerBlockSelected = useSelect(
( select ) => {
const { getSelectedBlockClientId, hasSelectedInnerBlock } =
select( 'core/block-editor' );

// Check if the current block is selected.
const isSelected = getSelectedBlockClientId() === clientId;

// Check if any inner block of the current block is selected.
const isInnerBlockSelected = hasSelectedInnerBlock(
clientId,
true
);

return isSelected || isInnerBlockSelected;
},
[ clientId ]
);

/**
* If inherit is not a boolean, then we haven't set default attributes yet.
* We don't wanna render anything until default attributes are set.
Expand All @@ -91,8 +131,29 @@ const ProductCollectionContent = (
return null;
}

// Let's not render anything until default attributes are set.
if ( ! isInitialAttributesSet.current ) {
return null;
}

return (
<div { ...blockProps }>
{ attributes.__privatePreviewState?.isPreview &&
isSelectedOrInnerBlockSelected && (
<Button
imanish003 marked this conversation as resolved.
Show resolved Hide resolved
variant="primary"
size="small"
showTooltip
label={
attributes.__privatePreviewState?.previewMessage
}
className="wc-block-product-collection__preview-button"
data-test-id="product-collection-preview-button"
>
Preview
</Button>
) }

<InspectorControls { ...props } />
<InspectorAdvancedControls { ...props } />
imanish003 marked this conversation as resolved.
Show resolved Hide resolved
<ToolbarControls { ...props } />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import CollectionChooserToolbar from './collection-chooser-toolbar';
import type { ProductCollectionEditComponentProps } from '../../types';

export default function ToolbarControls(
props: ProductCollectionEditComponentProps
props: Omit< ProductCollectionEditComponentProps, 'preview' >
) {
const { attributes, openCollectionSelectionModal, setAttributes } = props;
const { query, displayLayout } = attributes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import type { BlockEditProps } from '@wordpress/blocks';
import { type AttributeMetadata } from '@woocommerce/types';

/**
* Internal dependencies
*/
import { WooCommerceBlockLocation } from '../product-template/utils';

export interface ProductCollectionAttributes {
query: ProductCollectionQuery;
queryId: number;
Expand All @@ -23,6 +28,8 @@ export interface ProductCollectionAttributes {
*/
queryContextIncludes: string[];
forcePageReload: boolean;
// eslint-disable-next-line @typescript-eslint/naming-convention
__privatePreviewState?: PreviewState;
}

export enum LayoutOptions {
Expand Down Expand Up @@ -91,6 +98,10 @@ export interface ProductCollectionQuery {
export type ProductCollectionEditComponentProps =
BlockEditProps< ProductCollectionAttributes > & {
openCollectionSelectionModal: () => void;
preview: {
initialPreviewState?: PreviewState;
setPreviewState?: SetPreviewState;
};
context: {
templateSlug: string;
};
Expand Down Expand Up @@ -143,3 +154,14 @@ export enum CoreFilterNames {

export type CollectionName = CoreCollectionNames | string;
export type FilterName = CoreFilterNames | string;

export interface PreviewState {
isPreview: boolean;
previewMessage: string;
}

export type SetPreviewState = ( args: {
setState: ( previewState: PreviewState ) => void;
location: WooCommerceBlockLocation;
attributes: ProductCollectionAttributes;
} ) => void | ( () => void );