From 4ff24a0188921b585a53f9aac6c64ef65076712e Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Mon, 2 Sep 2024 11:09:18 +0100 Subject: [PATCH] Revert "CYS: Revert Zoom Feature (#50535)" This reverts commit 16ef8695875639aa308abe9594a65ad4dae9f41c. --- .../components/device-toolbar.tsx | 145 +++++++++++ .../context/zoom-out-context.tsx | 39 +++ .../hooks/use-popover-handler.ts | 2 +- .../customize-store/assembler-hub/iframe.jsx | 232 +++++++++++++++--- .../customize-store/assembler-hub/index.tsx | 14 +- .../customize-store/assembler-hub/layout.tsx | 34 ++- .../assembler-hub/resizable-frame.jsx | 29 ++- .../pattern-screen/sidebar-pattern-screen.tsx | 2 +- .../customize-store/assembler-hub/style.scss | 79 +++++- .../assembler-hub/toolbar/toolbar.tsx | 46 +++- .../add-cys-assembler-zoomed-out-view | 4 + .../changelog/add-cys-zoom-e2e-tests | 4 + .../woocommerce/changelog/fix-cys-animation | 4 + .../assembler/full-composability.spec.js | 34 +++ 14 files changed, 611 insertions(+), 57 deletions(-) create mode 100644 plugins/woocommerce-admin/client/customize-store/assembler-hub/components/device-toolbar.tsx create mode 100644 plugins/woocommerce-admin/client/customize-store/assembler-hub/context/zoom-out-context.tsx create mode 100644 plugins/woocommerce/changelog/add-cys-assembler-zoomed-out-view create mode 100644 plugins/woocommerce/changelog/add-cys-zoom-e2e-tests create mode 100644 plugins/woocommerce/changelog/fix-cys-animation diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/components/device-toolbar.tsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/components/device-toolbar.tsx new file mode 100644 index 00000000000..43114cace73 --- /dev/null +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/components/device-toolbar.tsx @@ -0,0 +1,145 @@ +/* eslint-disable @woocommerce/dependency-group */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/** + * External dependencies + */ +import clsx from 'clsx'; +import React from 'react'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useContext } from '@wordpress/element'; +// @ts-ignore No types for this exist yet. +import { store as editorStore } from '@wordpress/editor'; +import { Icon, desktop, tablet, mobile } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { + // @ts-ignore No types for this exist yet. + NavigableMenu, + Circle, + SVG, + Path, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { ZoomOutContext } from '../context/zoom-out-context'; + +const zoomIn = ( + + + + + +); + +const zoomOut = ( + + + + + +); + +const BUTTON_CLASS_NAMES = + 'components-button has-icon woocommerce-customize-store__device-button'; +const ICON_CLASS_NAMES = 'woocommerce-customize-store__device-icon'; + +export function DeviceToolbar( { isEditorLoading = false } ) { + // @ts-expect-error expect error + const { setDeviceType } = useDispatch( editorStore ); + const { toggleZoomOut, isZoomedOut } = useContext( ZoomOutContext ); + + const { deviceType } = useSelect( ( select ) => { + // @ts-ignore expect error + const { getDeviceType } = select( editorStore ); + + return { + deviceType: getDeviceType(), + }; + } ); + + // Zoom Out isn't available on mobile or tablet. + // In this case, we want to switch to desktop mode when zooming out. + const switchDeviceType = ( newDeviceType: string ) => { + if ( isZoomedOut ) { + toggleZoomOut(); + } + setDeviceType( newDeviceType ); + }; + + return ( + + + + + + + ); +} diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/context/zoom-out-context.tsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/context/zoom-out-context.tsx new file mode 100644 index 00000000000..f2c445f609b --- /dev/null +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/context/zoom-out-context.tsx @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +import React, { createContext, useState } from '@wordpress/element'; +import type { ReactNode } from 'react'; + +export const ZoomOutContext = createContext< { + isZoomedOut: boolean; + toggleZoomOut: () => void; +} >( { + isZoomedOut: false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + toggleZoomOut: () => { + // No op by default. + }, +} ); + +export const ZoomOutContextProvider = ( { + children, +}: { + children: ReactNode; +} ) => { + const [ isZoomedOut, setIsZoomedOut ] = useState< boolean >( false ); + + const toggleZoomOut = () => { + setIsZoomedOut( ! isZoomedOut ); + }; + + return ( + + { children } + + ); +}; diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/hooks/use-popover-handler.ts b/plugins/woocommerce-admin/client/customize-store/assembler-hub/hooks/use-popover-handler.ts index d0370121ef5..2477a68916d 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/hooks/use-popover-handler.ts +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/hooks/use-popover-handler.ts @@ -58,7 +58,7 @@ export const usePopoverHandler = () => { hoveredBlockClientId: string | null; } ) => { const iframe = window.document.querySelector( - '.woocommerce-customize-store-assembler > iframe[name="editor-canvas"]' + '.woocommerce-customize-store-assembler > .block-editor-iframe__container iframe[name="editor-canvas"]' ) as HTMLElement; const target = event.target as HTMLElement; diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/iframe.jsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/iframe.jsx index b4085be6d6f..bbb7480f22a 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/iframe.jsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/iframe.jsx @@ -1,5 +1,7 @@ // Reference: https://github.com/WordPress/gutenberg/blob/f91b4fb4a12e41dd39c9594f24ea1a1a4e23dade/packages/block-editor/src/components/iframe/index.js#L1 // We fork the code from the above link to reduce the unnecessary network requests and improve the performance. +// Some of the code is not used in the project and is removed. +// We've also made some changes to the code to make it work with the Zoom Out feature. /** * External dependencies @@ -11,6 +13,8 @@ import { forwardRef, useMemo, useEffect, + useRef, + useContext, } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { @@ -21,37 +25,42 @@ import { } from '@wordpress/compose'; import { __experimentalStyleProvider as StyleProvider } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; - import { store as blockEditorStore } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import { ZoomOutContext } from './context/zoom-out-context'; + function Iframe( { contentRef, children, tabIndex = 0, scale = 1, frameSize = 0, - expand = false, readonly, forwardedRef: ref, - loadStyles = true, - loadScripts = false, + title = __( 'Editor canvas', 'woocommerce' ), + canEnableZoomOutView = false, ...props } ) { - const [ iframeDocument, setIframeDocument ] = useState(); - - const [ bodyClasses, setBodyClasses ] = useState( [] ); - const { resolvedAssets } = useSelect( ( select ) => { - const settings = select( blockEditorStore ).getSettings(); - + const { getSettings } = select( blockEditorStore ); + const settings = getSettings(); return { resolvedAssets: settings.__unstableResolvedAssets, }; }, [] ); - const { styles = '', scripts = '' } = resolvedAssets; + const { styles = '', scripts = '' } = resolvedAssets; + const [ iframeDocument, setIframeDocument ] = useState(); + const prevContainerWidth = useRef( 0 ); + const [ bodyClasses, setBodyClasses ] = useState( [] ); const [ contentResizeListener, { height: contentHeight } ] = useResizeObserver(); + const [ containerResizeListener, { width: containerWidth } ] = + useResizeObserver(); + const setRef = useRefEffect( ( node ) => { node._load = () => { setIframeDocument( node.contentDocument ); @@ -62,6 +71,9 @@ function Iframe( { documentElement.classList.add( 'block-editor-iframe__html' ); + // Ideally ALL classes that are added through get_body_class should + // be added in the editor too, which we'll somehow have to get from + // the server in the future (which will run the PHP filters). setBodyClasses( Array.from( ownerDocument?.body.classList ).filter( ( name ) => @@ -70,6 +82,8 @@ function Iframe( { name === 'wp-embed-responsive' ) ); + + contentDocument.dir = ownerDocument.dir; } node.addEventListener( 'load', onLoad ); @@ -80,8 +94,53 @@ function Iframe( { }; }, [] ); + const [ iframeWindowInnerHeight, setIframeWindowInnerHeight ] = useState(); + + const iframeResizeRef = useRefEffect( ( node ) => { + const nodeWindow = node.ownerDocument.defaultView; + + setIframeWindowInnerHeight( nodeWindow.innerHeight ); + const onResize = () => { + setIframeWindowInnerHeight( nodeWindow.innerHeight ); + }; + nodeWindow.addEventListener( 'resize', onResize ); + return () => { + nodeWindow.removeEventListener( 'resize', onResize ); + }; + }, [] ); + + const [ windowInnerWidth, setWindowInnerWidth ] = useState(); + + const windowResizeRef = useRefEffect( ( node ) => { + const nodeWindow = node.ownerDocument.defaultView; + + setWindowInnerWidth( nodeWindow.innerWidth ); + const onResize = () => { + setWindowInnerWidth( nodeWindow.innerWidth ); + }; + nodeWindow.addEventListener( 'resize', onResize ); + return () => { + nodeWindow.removeEventListener( 'resize', onResize ); + }; + }, [] ); + + const isZoomedOut = scale !== 1 && canEnableZoomOutView; + + useEffect( () => { + if ( ! isZoomedOut && ! prevContainerWidth.current ) { + prevContainerWidth.current = containerWidth; + } + }, [ containerWidth, isZoomedOut ] ); + const disabledRef = useDisabled( { isDisabled: ! readonly } ); - const bodyRef = useMergeRefs( [ contentRef, disabledRef ] ); + const bodyRef = useMergeRefs( [ + contentRef, + disabledRef, + // Avoid resize listeners when not needed, these will trigger + // unnecessary re-renders when animating the iframe width, or when + // expanding preview iframes. + isZoomedOut ? iframeResizeRef : null, + ] ); // Correct doctype is required to enable rendering in standards // mode. Also preload the styles to avoid a flash of unstyled @@ -89,10 +148,23 @@ function Iframe( { const html = ` + - - ${ loadStyles ? styles : '' } - ${ loadScripts ? scripts : '' } + + ${ styles } + ${ scripts } @@ -108,30 +180,86 @@ function Iframe( { useEffect( () => cleanup, [ cleanup ] ); - // We need to counter the margin created by scaling the iframe. If the scale - // is e.g. 0.45, then the top + bottom margin is 0.55 (1 - scale). Just the - // top or bottom margin is 0.55 / 2 ((1 - scale) / 2). - const marginFromScaling = ( contentHeight * ( 1 - scale ) ) / 2; + useEffect( () => { + if ( ! canEnableZoomOutView || ! iframeDocument || ! isZoomedOut ) { + return; + } - return ( + const maxWidth = 800; + + iframeDocument.documentElement.classList.add( 'is-zoomed-out' ); + + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-scale', + scale === 'default' + ? Math.min( containerWidth, maxWidth ) / + prevContainerWidth.current + : scale + ); + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-frame-size', + typeof frameSize === 'number' ? `${ frameSize }px` : frameSize + ); + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-content-height', + `${ contentHeight }px` + ); + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-inner-height', + `${ iframeWindowInnerHeight }px` + ); + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-container-width', + `${ containerWidth }px` + ); + iframeDocument.documentElement.style.setProperty( + '--wp-block-editor-iframe-zoom-out-prev-container-width', + `${ prevContainerWidth.current }px` + ); + + return () => { + iframeDocument.documentElement.classList.remove( 'is-zoomed-out' ); + + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-scale' + ); + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-frame-size' + ); + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-content-height' + ); + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-inner-height' + ); + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-container-width' + ); + iframeDocument.documentElement.style.removeProperty( + '--wp-block-editor-iframe-zoom-out-prev-container-width' + ); + }; + }, [ + scale, + frameSize, + iframeDocument, + iframeWindowInnerHeight, + contentHeight, + containerWidth, + windowInnerWidth, + isZoomedOut, + canEnableZoomOutView, + ] ); + + const iframe = ( <> + { /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */ } ); + + return ( +
+ { containerResizeListener } +
+ { iframe } +
+
+ ); } function IframeIfReady( props, ref ) { @@ -172,6 +320,15 @@ function IframeIfReady( props, ref ) { [] ); + const { isZoomedOut } = useContext( ZoomOutContext ); + + const zoomOutProps = isZoomedOut + ? { + scale: 'default', + frameSize: '48px', + } + : {}; + // We shouldn't render the iframe until the editor settings are initialised. // The initial settings are needed to get the styles for the srcDoc, which // cannot be changed after the iframe is mounted. srcDoc is used to to set @@ -181,7 +338,12 @@ function IframeIfReady( props, ref ) { return null; } - return