From f873e073860dd70c11e535dcd41262936a22e866 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Mon, 29 Jan 2024 13:29:28 +0100 Subject: [PATCH] Product support and better rendering of columns --- .../ContentTypes/Block/Block.tsx | 2 +- .../ContentTypes/Column/Column.tsx | 18 +- .../ContentTypes/ColumnGroup/ColumnGroup.tsx | 15 +- .../ColumnGroup/columnGroupAggregator.ts | 5 +- .../ContentTypes/ColumnGroup/types.ts | 5 +- .../ContentTypes/ColumnLine/ColumnLine.tsx | 24 ++ .../ColumnLine/columnLineAggregator.ts | 8 + .../ContentTypes/ColumnLine/types.ts | 8 + .../Products/Carousel/carousel.gql.ce.js | 16 - .../Products/Carousel/carousel.gql.ee.js | 17 - .../Products/Carousel/carousel.js | 20 -- .../ContentTypes/Products/Carousel/index.js | 1 - .../Products/Carousel/useCarousel.js | 25 -- .../ContentTypes/Products/Products.tsx | 26 ++ .../ContentTypes/Products/configAggregator.js | 26 -- .../ContentTypes/Products/products.js | 325 ------------------ .../ContentTypes/Products/products.module.css | 58 ---- .../Products/productsAggregator.ts | 31 ++ .../ContentTypes/Products/types.ts | 20 ++ .../MediaBackground/VideoBackground.tsx | 6 +- packages/magento-pagebuilder/mesh.ts | 10 +- .../parser/{parser.tsx => parser.ts} | 14 +- packages/magento-pagebuilder/parserTypes.ts | 7 +- packages/magento-pagebuilder/renderTypes.ts | 5 +- packages/magento-pagebuilder/types.ts | 5 +- 25 files changed, 184 insertions(+), 513 deletions(-) create mode 100644 packages/magento-pagebuilder/ContentTypes/ColumnLine/ColumnLine.tsx create mode 100644 packages/magento-pagebuilder/ContentTypes/ColumnLine/columnLineAggregator.ts create mode 100644 packages/magento-pagebuilder/ContentTypes/ColumnLine/types.ts delete mode 100644 packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.gql.ce.js delete mode 100644 packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.gql.ee.js delete mode 100644 packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.js delete mode 100644 packages/magento-pagebuilder/ContentTypes/Products/Carousel/index.js delete mode 100644 packages/magento-pagebuilder/ContentTypes/Products/Carousel/useCarousel.js create mode 100644 packages/magento-pagebuilder/ContentTypes/Products/Products.tsx delete mode 100644 packages/magento-pagebuilder/ContentTypes/Products/configAggregator.js delete mode 100644 packages/magento-pagebuilder/ContentTypes/Products/products.js delete mode 100644 packages/magento-pagebuilder/ContentTypes/Products/products.module.css create mode 100644 packages/magento-pagebuilder/ContentTypes/Products/productsAggregator.ts create mode 100644 packages/magento-pagebuilder/ContentTypes/Products/types.ts rename packages/magento-pagebuilder/parser/{parser.tsx => parser.ts} (90%) diff --git a/packages/magento-pagebuilder/ContentTypes/Block/Block.tsx b/packages/magento-pagebuilder/ContentTypes/Block/Block.tsx index 80bdbf3840..201e29a4b6 100644 --- a/packages/magento-pagebuilder/ContentTypes/Block/Block.tsx +++ b/packages/magento-pagebuilder/ContentTypes/Block/Block.tsx @@ -14,7 +14,7 @@ export const Block: BlockContentType['component'] = (props) => { const { content } = additional return ( - + {content?.map((child, index) => ( // eslint-disable-next-line react/no-array-index-key diff --git a/packages/magento-pagebuilder/ContentTypes/Column/Column.tsx b/packages/magento-pagebuilder/ContentTypes/Column/Column.tsx index fc8f5746d7..d3a9162394 100644 --- a/packages/magento-pagebuilder/ContentTypes/Column/Column.tsx +++ b/packages/magento-pagebuilder/ContentTypes/Column/Column.tsx @@ -15,7 +15,15 @@ export const Column: ColumnContentType['component'] = (incoming) => { const [imageProps, props] = extractImageBackgroundProps(additional) const columnElement = useRef(null) - const { backgroundColor, children, minHeight, verticalAlignment, width, appearance } = props + const { + backgroundColor, + children, + minHeight, + verticalAlignment, + width, + appearance, + contentType, + } = additional // let image = desktopImage // if (mobileImage && matchMedia && matchMedia('(max-width: 768px)').matches) { @@ -104,7 +112,13 @@ export const Column: ColumnContentType['component'] = (incoming) => { // }, [backgroundSize, image, setBgImageStyle]) return ( - + {children} ) diff --git a/packages/magento-pagebuilder/ContentTypes/ColumnGroup/ColumnGroup.tsx b/packages/magento-pagebuilder/ContentTypes/ColumnGroup/ColumnGroup.tsx index ce7ee98d2b..9f85f01c47 100644 --- a/packages/magento-pagebuilder/ContentTypes/ColumnGroup/ColumnGroup.tsx +++ b/packages/magento-pagebuilder/ContentTypes/ColumnGroup/ColumnGroup.tsx @@ -1,5 +1,6 @@ import { Box } from '@mui/material' import { ColumnGroupContentType } from './types' +import { extractAdvancedProps } from '../../utils' /** * Page Builder ColumnGroup component. @@ -7,7 +8,17 @@ import { ColumnGroupContentType } from './types' * This component is part of the Page Builder / PWA integration. It can be consumed without Page Builder. */ export const ColumnGroup: ColumnGroupContentType['component'] = (props) => { - const { display, children } = props + const [cssProps, cssClasses, additional] = extractAdvancedProps(props) + const { children, appearance, contentType } = additional - return {children} + return ( + + {children} + + ) } diff --git a/packages/magento-pagebuilder/ContentTypes/ColumnGroup/columnGroupAggregator.ts b/packages/magento-pagebuilder/ContentTypes/ColumnGroup/columnGroupAggregator.ts index 282e5f0928..0b1c3f0a61 100644 --- a/packages/magento-pagebuilder/ContentTypes/ColumnGroup/columnGroupAggregator.ts +++ b/packages/magento-pagebuilder/ContentTypes/ColumnGroup/columnGroupAggregator.ts @@ -1,6 +1,7 @@ -import { getIsHidden } from '../../utils' -import { ColumnGroupContentType } from './types' +import { getAdvanced } from '../../utils' +import type { ColumnGroupContentType } from './types' export const columnGroupAggregator: ColumnGroupContentType['configAggregator'] = (node) => ({ display: node.style.display, + ...getAdvanced(node), }) diff --git a/packages/magento-pagebuilder/ContentTypes/ColumnGroup/types.ts b/packages/magento-pagebuilder/ContentTypes/ColumnGroup/types.ts index bb0f747ef1..33c5810796 100644 --- a/packages/magento-pagebuilder/ContentTypes/ColumnGroup/types.ts +++ b/packages/magento-pagebuilder/ContentTypes/ColumnGroup/types.ts @@ -1,7 +1,8 @@ import { ContentType, ContentTypeConfig } from '../../types' +import { AdvancedProps } from '../../utils' type ColumnGroupConfig = ContentTypeConfig<'column-group'> -export type ButtonItemProps = Pick +export type ColumnGroupProps = AdvancedProps & Pick -export type ColumnGroupContentType = ContentType +export type ColumnGroupContentType = ContentType diff --git a/packages/magento-pagebuilder/ContentTypes/ColumnLine/ColumnLine.tsx b/packages/magento-pagebuilder/ContentTypes/ColumnLine/ColumnLine.tsx new file mode 100644 index 0000000000..dfe773b868 --- /dev/null +++ b/packages/magento-pagebuilder/ContentTypes/ColumnLine/ColumnLine.tsx @@ -0,0 +1,24 @@ +import { Box } from '@mui/material' +import { extractAdvancedProps } from '../../utils' +import { ColumnLineContentType } from './types' + +/** + * Page Builder ColumnGroup component. + * + * This component is part of the Page Builder / PWA integration. It can be consumed without Page Builder. + */ +export const ColumnLine: ColumnLineContentType['component'] = (props) => { + const [cssProps, cssClasses, additional] = extractAdvancedProps(props) + const { children, appearance, contentType, display, width, sx } = additional + + return ( + + {children} + + ) +} diff --git a/packages/magento-pagebuilder/ContentTypes/ColumnLine/columnLineAggregator.ts b/packages/magento-pagebuilder/ContentTypes/ColumnLine/columnLineAggregator.ts new file mode 100644 index 0000000000..8281bd9d1f --- /dev/null +++ b/packages/magento-pagebuilder/ContentTypes/ColumnLine/columnLineAggregator.ts @@ -0,0 +1,8 @@ +import { getAdvanced } from '../../utils' +import type { ColumnLineContentType } from './types' + +export const columnLineAggregator: ColumnLineContentType['configAggregator'] = (node) => ({ + display: node.style.display, + width: node.style.width, + ...getAdvanced(node), +}) diff --git a/packages/magento-pagebuilder/ContentTypes/ColumnLine/types.ts b/packages/magento-pagebuilder/ContentTypes/ColumnLine/types.ts new file mode 100644 index 0000000000..e29bceba81 --- /dev/null +++ b/packages/magento-pagebuilder/ContentTypes/ColumnLine/types.ts @@ -0,0 +1,8 @@ +import { ContentType, ContentTypeConfig } from '../../types' +import { AdvancedProps } from '../../utils' + +type ColumnLineConfig = ContentTypeConfig<'column-group'> + +export type ColumnLineProps = AdvancedProps & Pick + +export type ColumnLineContentType = ContentType diff --git a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.gql.ce.js b/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.gql.ce.js deleted file mode 100644 index b9cf6e64b9..0000000000 --- a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.gql.ce.js +++ /dev/null @@ -1,16 +0,0 @@ -import { gql } from '@apollo/client'; - -export const GET_STORE_CONFIG = gql` - query GetStoreConfigForCarouselCE { - # eslint-disable-next-line @graphql-eslint/require-id-when-available - storeConfig { - store_code - product_url_suffix - magento_wishlist_general_is_enabled - } - } -`; - -export default { - getStoreConfigQuery: GET_STORE_CONFIG -}; diff --git a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.gql.ee.js b/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.gql.ee.js deleted file mode 100644 index 10c545dec9..0000000000 --- a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.gql.ee.js +++ /dev/null @@ -1,17 +0,0 @@ -import { gql } from '@apollo/client'; - -export const GET_STORE_CONFIG = gql` - query GetStoreConfigForCarouselEE { - # eslint-disable-next-line @graphql-eslint/require-id-when-available - storeConfig { - store_code - product_url_suffix - magento_wishlist_general_is_enabled - enable_multiple_wishlists - } - } -`; - -export default { - getStoreConfigQuery: GET_STORE_CONFIG -}; diff --git a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.js b/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.js deleted file mode 100644 index c7fcc45586..0000000000 --- a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/carousel.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import SlickSlider from 'react-slick'; -import GalleryItem from '@magento/venia-ui/lib/components/Gallery/item'; -import { useCarousel } from './useCarousel'; - -const Carousel = props => { - const { settings, items } = props; - - const { storeConfig } = useCarousel(); - - const galleryItems = items.map((item, index) => { - return ( - - ); - }); - - return {galleryItems}; -}; - -export default Carousel; diff --git a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/index.js b/packages/magento-pagebuilder/ContentTypes/Products/Carousel/index.js deleted file mode 100644 index 90f847988d..0000000000 --- a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './carousel'; diff --git a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/useCarousel.js b/packages/magento-pagebuilder/ContentTypes/Products/Carousel/useCarousel.js deleted file mode 100644 index 25998c955c..0000000000 --- a/packages/magento-pagebuilder/ContentTypes/Products/Carousel/useCarousel.js +++ /dev/null @@ -1,25 +0,0 @@ -import { useQuery } from '@apollo/client'; - -import { useCustomerWishlistSkus } from '@magento/peregrine/lib/hooks/useCustomerWishlistSkus/useCustomerWishlistSkus'; - -import mergeOperations from '@magento/peregrine/lib/util/shallowMerge'; -import defaultOperations from './carousel.gql'; - -/** - * This is a duplicate of @magento/peregrine/lib/talons/Gallery/useGallery.js - */ -export const useCarousel = (props = {}) => { - const operations = mergeOperations(defaultOperations, props.operations); - - useCustomerWishlistSkus(); - - const { data: storeConfigData } = useQuery(operations.getStoreConfigQuery, { - fetchPolicy: 'cache-and-network' - }); - - const storeConfig = storeConfigData ? storeConfigData.storeConfig : null; - - return { - storeConfig - }; -}; diff --git a/packages/magento-pagebuilder/ContentTypes/Products/Products.tsx b/packages/magento-pagebuilder/ContentTypes/Products/Products.tsx new file mode 100644 index 0000000000..dc594db701 --- /dev/null +++ b/packages/magento-pagebuilder/ContentTypes/Products/Products.tsx @@ -0,0 +1,26 @@ +import { ProductScroller } from '@graphcommerce/magento-product' +import { Container } from '@mui/material' +import { + ProductListItems, + productListRenderer, +} from '../../../../examples/magento-graphcms/components' +import { extractAdvancedProps } from '../../utils' +import { ProductsContentType, ProductsProps } from './types' + +export const Products: ProductsContentType['component'] = (props) => { + const [cssProps, cssClasses, additional] = extractAdvancedProps(props) + const { appearance, products } = additional + + if (appearance === 'grid') { + return ( + + + + ) + } + return ( + + + + ) +} diff --git a/packages/magento-pagebuilder/ContentTypes/Products/configAggregator.js b/packages/magento-pagebuilder/ContentTypes/Products/configAggregator.js deleted file mode 100644 index 65b2de56f0..0000000000 --- a/packages/magento-pagebuilder/ContentTypes/Products/configAggregator.js +++ /dev/null @@ -1,26 +0,0 @@ -import { getAdvanced } from '../../utils' - -export default (node, props) => { - const forms = node.querySelectorAll( - '.product-item-details > .product-item-name > a.product-item-link', - ) - let carouselConfig = {} - - if (props.appearance === 'carousel') { - carouselConfig = { - autoplay: node.getAttribute('data-autoplay') === 'true', - autoplaySpeed: parseInt(node.getAttribute('data-autoplay-speed')), - infinite: node.getAttribute('data-infinite-loop') === 'true', - arrows: node.getAttribute('data-show-arrows') === 'true', - dots: node.getAttribute('data-show-dots') === 'true', - carouselMode: node.getAttribute('data-carousel-mode'), - centerPadding: node.getAttribute('data-center-padding'), - } - } - - return { - pathNames: [...forms].map((form) => form.getAttribute('href')), - ...carouselConfig, - ...getAdvanced(node), - } -} diff --git a/packages/magento-pagebuilder/ContentTypes/Products/products.js b/packages/magento-pagebuilder/ContentTypes/Products/products.js deleted file mode 100644 index 252b1ca2dd..0000000000 --- a/packages/magento-pagebuilder/ContentTypes/Products/products.js +++ /dev/null @@ -1,325 +0,0 @@ -import React, { useMemo } from 'react'; -import { gql, useQuery } from '@apollo/client'; -import { arrayOf, bool, number, oneOf, shape, string } from 'prop-types'; - -import { useStyle } from '@magento/venia-ui/lib/classify'; -import Gallery from '@magento/venia-ui/lib/components/Gallery'; -import Carousel from './Carousel/carousel'; -import defaultClasses from './products.module.css'; -/** - * Sort products based on the original order - * - * @param {Array} urlKeys - * @param {Array} products - * @returns {Array} - */ -const restoreSortOrder = (urlKeys, products) => { - const productsByOriginalOrder = new Map(); - products.forEach(product => { - productsByOriginalOrder.set(product.url_key, product); - }); - return urlKeys - .map(urlKey => productsByOriginalOrder.get(urlKey)) - .filter(Boolean); -}; - -/** - * Page Builder Products component. - * - * This component is part of the Page Builder / PWA integration. It can be consumed without Page Builder. - * - * @typedef Products - * @kind functional component - * - * @param {props} props React component props - * - * @returns {React.Element} A React component that displays a Products based on a number of products - */ -const Products = props => { - const classes = useStyle(defaultClasses, props.classes); - const { - appearance, - autoplay, - autoplaySpeed, - infinite, - arrows, - dots, - draggable = false, - carouselMode, - centerPadding, - pathNames = [], - textAlign, - border, - borderColor, - borderWidth, - borderRadius, - marginTop, - marginRight, - marginBottom, - marginLeft, - paddingTop, - paddingRight, - paddingBottom, - paddingLeft, - cssClasses = [], - slidesToShow = 5, - slidesToShowMedium = 4, - slidesToShowSmall = 2, - slidesToShowSmallCenterMode = 1 - } = props; - - const dynamicStyles = { - textAlign, - border, - borderColor, - borderWidth, - borderRadius, - marginTop, - marginRight, - marginBottom, - marginLeft, - paddingTop, - paddingRight, - paddingBottom, - paddingLeft - }; - - const { data: storeConfigData } = useQuery(GET_STORE_CONFIG_DATA, { - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'cache-first' - }); - - const productUrlSuffix = useMemo(() => { - if (storeConfigData) { - return storeConfigData.storeConfig.product_url_suffix; - } - }, [storeConfigData]); - - const urlKeys = pathNames.map(pathName => { - const slug = pathName.split('/').pop(); - return productUrlSuffix ? slug.replace(productUrlSuffix, '') : slug; - }); - - const { loading, error, data } = useQuery(GET_PRODUCTS_BY_URL_KEY, { - variables: { url_keys: urlKeys, pageSize: urlKeys.length } - }); - - if (loading) return null; - - if (error || data.products.items.length === 0) { - return null; - } - - const items = restoreSortOrder(urlKeys, data.products.items); - - if (appearance === 'carousel') { - //Settings conditions was made due to react-slick issues - const carouselCenterMode = - carouselMode === 'continuous' && items.length > slidesToShow; - const carouselSmallCenterMode = - carouselMode === 'continuous' && - items.length > slidesToShowSmallCenterMode; - const carouselSettings = { - slidesToShow, - slidesToScroll: slidesToShow, - draggable, - autoplay, - autoplaySpeed, - arrows, - dots, - centerMode: carouselCenterMode, - responsive: [ - { - breakpoint: 640, - settings: { - slidesToShow: carouselSmallCenterMode - ? slidesToShowSmallCenterMode - : slidesToShowSmall, - slidesToScroll: carouselSmallCenterMode - ? slidesToShowSmallCenterMode - : slidesToShowSmall, - centerMode: carouselSmallCenterMode, - ...(carouselSmallCenterMode && { centerPadding }), - ...{ - infinite: - items.length > slidesToShowSmall && infinite - } - } - }, - { - breakpoint: 960, - settings: { - slidesToShow: slidesToShowSmall + 1, - slidesToScroll: slidesToShowSmall + 1 - } - }, - { - breakpoint: 1280, - settings: { - slidesToShow: slidesToShowMedium, - slidesToScroll: slidesToShowMedium - } - } - ], - ...(carouselCenterMode && { centerPadding }), - ...{ infinite: items.length > slidesToShow && infinite } - }; - - const centerModeClass = carouselCenterMode ? classes.centerMode : null; - const centerModeSmallClass = carouselSmallCenterMode - ? classes.centerModeSmall - : null; - - return ( -
- -
- ); - } - - return ( -
- -
- ); -}; - -/** - * Props for {@link Products} - * - * @typedef props - * - * @property {Object} classes An object containing the class names for the Products - * @property {String} classes.root CSS class for products - * @property {String} classes.carousel CSS class for products carousel appearance - * @property {String} classes.centerMode CSS class for products carousel appearance with center mode - * @property {String} classes.centerModeSmall CSS class for products carousel appearance with center mode on small screen - * @property {String} classes.galleryItems CSS class to modify child gallery items - * @property {String} classes.error CSS class for displaying fetch errors - * @property {String} appearance Sets products appearance - * @property {Boolean} autoplay Whether the carousel should autoplay - * @property {Number} autoplaySpeed The speed at which the autoplay should move the slide on - * @property {Boolean} infinite Whether to infinitely scroll the carousel - * @property {Boolean} arrows Whether to show arrows on the slide for navigation - * @property {Boolean} dots Whether to show navigation dots at the bottom of the carousel - * @property {Boolean} draggable Enable scrollable via dragging on desktop - * @property {String} carouselMode Carousel mode - * @property {String} centerPadding Horizontal padding in centerMode - * @property {Array} pathNames List of Url path names to load into product list - * @property {String} textAlign Alignment of content within the products list - * @property {String} border CSS border property - * @property {String} borderColor CSS border color property - * @property {String} borderWidth CSS border width property - * @property {String} borderRadius CSS border radius property - * @property {String} marginTop CSS margin top property - * @property {String} marginRight CSS margin right property - * @property {String} marginBottom CSS margin bottom property - * @property {String} marginLeft CSS margin left property - * @property {String} paddingTop CSS padding top property - * @property {String} paddingRight CSS padding right property - * @property {String} paddingBottom CSS padding bottom property - * @property {String} paddingLeft CSS padding left property - * @property {Array} cssClasses List of CSS classes to be applied to the component - * @property {Number} slidesToShow # of slides to show at a time - * @property {Number} slidesToShowMedium # of slides to show at a time on medium sized screens - * @property {Number} slidesToShowSmall # of slides to show at a time on small screen - * @property {Number} slidesToShowSmallCenterMode # of slides to show at a time on small screen in centerMode - */ -Products.propTypes = { - classes: shape({ - root: string, - carousel: string, - centerMode: string, - centerModeSmall: string, - galleryItems: string, - error: string - }), - appearance: oneOf(['grid', 'carousel']), - autoplay: bool, - autoplaySpeed: number, - infinite: bool, - arrows: bool, - dots: bool, - draggable: bool, - carouselMode: oneOf(['default', 'continuous']), - centerPadding: string, - pathNames: arrayOf(string), - textAlign: string, - border: string, - borderColor: string, - borderWidth: string, - borderRadius: string, - marginTop: string, - marginRight: string, - marginBottom: string, - marginLeft: string, - paddingTop: string, - paddingRight: string, - paddingBottom: string, - paddingLeft: string, - cssClasses: arrayOf(string), - slidesToShow: number, - slidesToShowMedium: number, - slidesToShowSmall: number, - slidesToShowSmallCenterMode: number -}; - -export default Products; - -export const GET_PRODUCTS_BY_URL_KEY = gql` - query getProductsByUrlKey($url_keys: [String], $pageSize: Int!) { - products(filter: { url_key: { in: $url_keys } }, pageSize: $pageSize) { - items { - id - uid - name - price_range { - maximum_price { - regular_price { - currency - value - } - } - } - sku - small_image { - url - } - stock_status - __typename - url_key - } - total_count - filters { - name - filter_items_count - request_var - filter_items { - label - value_string - } - } - } - } -`; - -export const GET_STORE_CONFIG_DATA = gql` - query getStoreConfigData { - # eslint-disable-next-line @graphql-eslint/require-id-when-available - storeConfig { - store_code - product_url_suffix - } - } -`; diff --git a/packages/magento-pagebuilder/ContentTypes/Products/products.module.css b/packages/magento-pagebuilder/ContentTypes/Products/products.module.css deleted file mode 100644 index 1b32ff1ec1..0000000000 --- a/packages/magento-pagebuilder/ContentTypes/Products/products.module.css +++ /dev/null @@ -1,58 +0,0 @@ -.root { -} -.root a { - text-decoration: none; -} - -.error { - text-align: center; - padding: 2rem 0; -} - -.centerMode { -} - -.centerMode :global .slick-slide { - opacity: 0.5; - transition: 0.3s; -} - -.centerMode :global .slick-slide:hover { - opacity: 1; -} - -.centerMode :global .slick-slide.slick-current { - opacity: 1; -} - -.galleryItems { - composes: items from '~@magento/venia-ui/lib/components/Gallery/gallery.module.css'; - grid-template-columns: repeat(5, 1fr); -} - -@media (max-width: 640px) { - .galleryItems { - grid-template-columns: repeat(2, 1fr); - } - - .centerModeSmall { - } - - .centerModeSmall :global .slick-slide { - opacity: 0.5; - transition: 0.3s; - } - - .centerModeSmall :global .slick-slide:hover { - opacity: 1; - } - - .centerModeSmall :global .slick-slide.slick-current { - opacity: 1; - } -} - -.carousel { - composes: root; - composes: root from '../Slider/slider.module.css'; -} diff --git a/packages/magento-pagebuilder/ContentTypes/Products/productsAggregator.ts b/packages/magento-pagebuilder/ContentTypes/Products/productsAggregator.ts new file mode 100644 index 0000000000..8b7d7dba11 --- /dev/null +++ b/packages/magento-pagebuilder/ContentTypes/Products/productsAggregator.ts @@ -0,0 +1,31 @@ +import { Maybe, execute } from '@graphcommerce/graphql-mesh' +import { ProductListDocument, ProductListQuery } from '@graphcommerce/magento-product' +import { filterNonNullableKeys, nonNullable } from '@graphcommerce/next-ui' +import { getAdvanced } from '../../utils' +import type { ProductsContentType } from './types' + +export const productsAggregator: ProductsContentType['configAggregator'] = async (node, props) => { + const skus = [...node.querySelectorAll('[data-product-sku]')] + .map((el) => el.dataset.productSku) + .filter(nonNullable) + + const products = filterNonNullableKeys( + ( + (await execute(ProductListDocument, { filters: { sku: { in: skus } } })) + .data as Maybe + )?.products?.items, + ) + + return { + ...props, + products, + autoplay: node.getAttribute('data-autoplay') === 'true', + autoplaySpeed: parseInt(node.getAttribute('data-autoplay-speed') ?? '0', 10), + infinite: node.getAttribute('data-infinite-loop') === 'true', + arrows: node.getAttribute('data-show-arrows') === 'true', + dots: node.getAttribute('data-show-dots') === 'true', + carouselMode: node.getAttribute('data-carousel-mode') ?? 'default', + centerPadding: node.getAttribute('data-center-padding') ?? '', + ...getAdvanced(node), + } +} diff --git a/packages/magento-pagebuilder/ContentTypes/Products/types.ts b/packages/magento-pagebuilder/ContentTypes/Products/types.ts new file mode 100644 index 0000000000..27dcfebc60 --- /dev/null +++ b/packages/magento-pagebuilder/ContentTypes/Products/types.ts @@ -0,0 +1,20 @@ +import { ProductListItemFragment } from '@graphcommerce/magento-product' +import type { ContentType, ContentTypeConfig } from '../../types' +import { AdvancedProps } from '../../utils' + +type ProductsConfig = ContentTypeConfig<'products'> & { + appearance: 'grid' | 'carousel' +} + +export type ProductsProps = AdvancedProps & { + products: ProductListItemFragment[] + autoplay: boolean + autoplaySpeed: number + infinite: boolean + arrows: boolean + dots: boolean + carouselMode: string + centerPadding: string +} + +export type ProductsContentType = ContentType diff --git a/packages/magento-pagebuilder/components/MediaBackground/VideoBackground.tsx b/packages/magento-pagebuilder/components/MediaBackground/VideoBackground.tsx index a3187e7e72..51848580d5 100644 --- a/packages/magento-pagebuilder/components/MediaBackground/VideoBackground.tsx +++ b/packages/magento-pagebuilder/components/MediaBackground/VideoBackground.tsx @@ -34,6 +34,10 @@ export function VideoBackground(props: VideoBackgroundComponentProps) { const [, youtubeId] = youtubeRegExp.exec(videoSrc) ?? [] const [, vimeoId] = vimeoRegExp.exec(videoSrc) ?? [] + // File type urls are in the format mp4:http://example.com/video.mp4 + const [format, ...urlParts] = videoSrc.split(':') + const videoUrl = urlParts.join(':') + return ( + )} ) diff --git a/packages/magento-pagebuilder/mesh.ts b/packages/magento-pagebuilder/mesh.ts index e513362a19..d3ca1a2c15 100644 --- a/packages/magento-pagebuilder/mesh.ts +++ b/packages/magento-pagebuilder/mesh.ts @@ -30,7 +30,7 @@ export const resolvers: Resolvers = { selectionSet: `{ content }`, resolve: ({ content }, _, ctx) => { warnNoContent(content, 'content', 'CmsPage', ctx) - return parser(content) + return parser(content, ctx) }, }, content: ({ content }) => nullIfPagebuilder(content), @@ -40,7 +40,7 @@ export const resolvers: Resolvers = { selectionSet: `{ content }`, resolve: ({ content }, _, ctx) => { warnNoContent(content, 'content', 'CmsBlock', ctx) - return parser(content) + return parser(content, ctx) }, }, content: ({ content }) => nullIfPagebuilder(content), @@ -50,17 +50,17 @@ export const resolvers: Resolvers = { selectionSet: `{ description }`, resolve: ({ description }, _, ctx) => { warnNoContent(description, 'description', 'CategoryTree', ctx) - return parser(description) + return parser(description, ctx) }, }, - description: ({ description }) => nullIfPagebuilder(description), + // description: ({ description }) => nullIfPagebuilder(description), }, ComplexTextValue: { pagebuilder: { selectionSet: `{ html }`, resolve: ({ html }, _, ctx) => { warnNoContent(html, 'html', 'ComplexTextValue', ctx) - return parser(html) + return parser(html, ctx) }, }, html: ({ html }) => nullIfPagebuilder(html) ?? '', diff --git a/packages/magento-pagebuilder/parser/parser.tsx b/packages/magento-pagebuilder/parser/parser.ts similarity index 90% rename from packages/magento-pagebuilder/parser/parser.tsx rename to packages/magento-pagebuilder/parser/parser.ts index baba43ac15..57babe8f63 100644 --- a/packages/magento-pagebuilder/parser/parser.tsx +++ b/packages/magento-pagebuilder/parser/parser.ts @@ -7,6 +7,7 @@ import { ContentTypeConfig } from '../types' // eslint-disable-next-line import/no-cycle import { getIsHidden, isHTMLElement } from '../utils' import { detectPageBuilder } from './detectPageBuilder' +import { MeshContext } from '../../../examples/magento-graphcms/.mesh' const pbStyleAttribute = 'data-pb-style' const bodyId = 'html-body' @@ -18,9 +19,10 @@ export const createContentTypeObject = (type: string, node?: HTMLElement): Conte }) /** Walk over tree nodes extracting each content types configuration */ -export const walk = ( +export const walk = async ( rootEl: Node, contentTypeStructureObj: ContentTypeConfig, + context: MeshContext, treeWalkerCb: (node: Node) => TreeWalker, ) => { const tree = treeWalkerCb(rootEl) @@ -40,11 +42,13 @@ export const walk = ( } const props = createContentTypeObject(contentType, currentNode) + const aggregator = getContentType(contentType) if (aggregator && typeof aggregator === 'function') { try { - const result = { ...props, ...aggregator(currentNode, props) } + // eslint-disable-next-line no-await-in-loop + const result = { ...props, ...(await aggregator(currentNode, props, context)) } if (!getIsHidden(currentNode)) contentTypeStructureObj.children.push(result) } catch (e) { @@ -56,7 +60,7 @@ export const walk = ( ) } - walk(currentNode, props, treeWalkerCb) + await walk(currentNode, props, context, treeWalkerCb) currentNode = tree.nextSibling() } @@ -126,7 +130,7 @@ export const convertToInlineStyles = (document: HTMLElement | Document) => { }) } -export const parser = (htmlStr: string | null | undefined) => { +export const parser = (htmlStr: string | null | undefined, context: MeshContext) => { if (!detectPageBuilder(htmlStr)) return null const jsdom = new JSDOM(`${htmlStr}`) @@ -140,7 +144,7 @@ export const parser = (htmlStr: string | null | undefined) => { body.id = bodyId convertToInlineStyles(jsdom.window.document) - return walk(body, stageContentType, (rootEl) => + return walk(body, stageContentType, context, (rootEl) => document.createTreeWalker( rootEl, // eslint-disable-next-line no-bitwise diff --git a/packages/magento-pagebuilder/parserTypes.ts b/packages/magento-pagebuilder/parserTypes.ts index da7bc24da7..6ee7dbc450 100644 --- a/packages/magento-pagebuilder/parserTypes.ts +++ b/packages/magento-pagebuilder/parserTypes.ts @@ -4,17 +4,19 @@ import { buttonItemAggregator } from './ContentTypes/ButtonItem/buttonItemAggreg import { buttonsAggregator } from './ContentTypes/Buttons/buttonsAggregator' import { columnAggregator } from './ContentTypes/Column/columnAggregator' import { columnGroupAggregator } from './ContentTypes/ColumnGroup/columnGroupAggregator' +import { columnLineAggregator } from './ContentTypes/ColumnLine/columnLineAggregator' import { dividerAggregator } from './ContentTypes/Divider/dividerAggregator' import { headingAggregator } from './ContentTypes/Heading/headingAggregator' import { htmlAggregator } from './ContentTypes/Html/htmlAggregator' import { imageAggregator } from './ContentTypes/Image/imageAggregator' +import { productsAggregator } from './ContentTypes/Products/productsAggregator' import { rowAggregator } from './ContentTypes/Row/rowAggregator' import { sliderAggregator } from './ContentTypes/Slider/sliderAggregator' import { tabItemAggregator } from './ContentTypes/TabItem/tabItemAggregator' import { tabsAggregator } from './ContentTypes/Tabs/tabsAggregator' import { textAggregator } from './ContentTypes/Text/textAggregator' import { videoAggregator } from './ContentTypes/Video/videoAggregator' -import { ParseProps, ContentTypeConfig } from './types' +import { ParseProps } from './types' type ContentTypes = | 'row' @@ -39,6 +41,7 @@ type ContentTypes = export const contentTypes = { row: rowAggregator, column: columnAggregator, + 'column-line': columnLineAggregator, 'column-group': columnGroupAggregator, image: imageAggregator, heading: headingAggregator, @@ -49,7 +52,7 @@ export const contentTypes = { 'button-item': buttonItemAggregator, block: blockAggregator, // dynamic_block, - // products, + products: productsAggregator, html: htmlAggregator, divider: dividerAggregator, video: videoAggregator, diff --git a/packages/magento-pagebuilder/renderTypes.ts b/packages/magento-pagebuilder/renderTypes.ts index b0cfdd3ee0..d83c775392 100644 --- a/packages/magento-pagebuilder/renderTypes.ts +++ b/packages/magento-pagebuilder/renderTypes.ts @@ -5,10 +5,12 @@ import { ButtonItem } from './ContentTypes/ButtonItem/ButtonItem' import { Buttons } from './ContentTypes/Buttons/Buttons' import { Column } from './ContentTypes/Column/Column' import { ColumnGroup } from './ContentTypes/ColumnGroup/ColumnGroup' +import { ColumnLine } from './ContentTypes/ColumnLine/ColumnLine' import { Divider } from './ContentTypes/Divider/Divider' import { Heading } from './ContentTypes/Heading/Heading' import { Html } from './ContentTypes/Html/Html' import { Image } from './ContentTypes/Image/Image' +import { Products } from './ContentTypes/Products/Products' import { RootContainer } from './ContentTypes/RootContainer/RootContainer' import { Row } from './ContentTypes/Row/Row' import { Slider } from './ContentTypes/Slider/Slider' @@ -23,6 +25,7 @@ const renderTypes = { 'root-container': RootContainer, row: Row, column: Column, + 'column-line': ColumnLine, 'column-group': ColumnGroup, image: Image, heading: Heading, @@ -33,7 +36,7 @@ const renderTypes = { 'button-item': ButtonItem, block: Block, // dynamic_block, - // products, + products: Products, html: Html, divider: Divider, video: Video, diff --git a/packages/magento-pagebuilder/types.ts b/packages/magento-pagebuilder/types.ts index d635ba730b..4a169e5c98 100644 --- a/packages/magento-pagebuilder/types.ts +++ b/packages/magento-pagebuilder/types.ts @@ -1,4 +1,5 @@ import React from 'react' +import { MeshContext } from '../../examples/magento-graphcms/.mesh' export type ContentTypeConfigChildren = Array @@ -13,11 +14,11 @@ export type ContentTypeConfig = { export type ParseProps< Config extends ContentTypeConfig = ContentTypeConfig, R extends Record = Record, -> = (node: HTMLElement, config: Config) => R +> = (node: HTMLElement, config: Config, context: MeshContext) => R | Promise export type ContentType> = { configAggregator: ParseProps - component: React.FC + component: React.FC & { children: React.ReactNode }> } export type RenderComponent = React.FC<