[Customize your store] Fix site preview in transitional screen (#40588)
* Replace mshot image with preview editor frame in transitional screen * Add changelog * Fix test * Use current xstate to check if it is transitional page
This commit is contained in:
parent
a28a552bbf
commit
06ea7ae24a
|
@ -47,7 +47,8 @@ export type ScaledBlockPreviewProps = {
|
||||||
};
|
};
|
||||||
additionalStyles: string;
|
additionalStyles: string;
|
||||||
onClickNavigationItem: ( event: MouseEvent ) => void;
|
onClickNavigationItem: ( event: MouseEvent ) => void;
|
||||||
isNavigable: boolean;
|
isNavigable?: boolean;
|
||||||
|
isScrollable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ScaledBlockPreview( {
|
function ScaledBlockPreview( {
|
||||||
|
@ -57,6 +58,7 @@ function ScaledBlockPreview( {
|
||||||
additionalStyles,
|
additionalStyles,
|
||||||
onClickNavigationItem,
|
onClickNavigationItem,
|
||||||
isNavigable = false,
|
isNavigable = false,
|
||||||
|
isScrollable = true,
|
||||||
}: ScaledBlockPreviewProps ) {
|
}: ScaledBlockPreviewProps ) {
|
||||||
const { setLogoBlock } = useContext( LogoBlockContext );
|
const { setLogoBlock } = useContext( LogoBlockContext );
|
||||||
const [ fontFamilies ] = useGlobalSetting(
|
const [ fontFamilies ] = useGlobalSetting(
|
||||||
|
@ -78,6 +80,7 @@ function ScaledBlockPreview( {
|
||||||
<DisabledProvider value={ true }>
|
<DisabledProvider value={ true }>
|
||||||
<Iframe
|
<Iframe
|
||||||
aria-hidden
|
aria-hidden
|
||||||
|
scrolling={ isScrollable ? 'yes' : 'no' }
|
||||||
tabIndex={ -1 }
|
tabIndex={ -1 }
|
||||||
readonly={ ! isNavigable }
|
readonly={ ! isNavigable }
|
||||||
contentRef={ useRefEffect(
|
contentRef={ useRefEffect(
|
||||||
|
|
|
@ -14,15 +14,16 @@ import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||||
// @ts-ignore No types for this exist yet.
|
// @ts-ignore No types for this exist yet.
|
||||||
import useSiteEditorSettings from '@wordpress/edit-site/build-module/components/block-editor/use-site-editor-settings';
|
import useSiteEditorSettings from '@wordpress/edit-site/build-module/components/block-editor/use-site-editor-settings';
|
||||||
|
import { useQuery } from '@woocommerce/navigation';
|
||||||
|
import { useContext, useCallback } from '@wordpress/element';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import BlockPreview from './block-preview';
|
import BlockPreview from './block-preview';
|
||||||
import { useCallback } from '@wordpress/element';
|
|
||||||
import { useEditorBlocks } from './hooks/use-editor-blocks';
|
import { useEditorBlocks } from './hooks/use-editor-blocks';
|
||||||
import { useScrollOpacity } from './hooks/use-scroll-opacity';
|
import { useScrollOpacity } from './hooks/use-scroll-opacity';
|
||||||
import { useQuery } from '@woocommerce/navigation';
|
import { CustomizeStoreContext } from './';
|
||||||
|
|
||||||
const { useHistory } = unlock( routerPrivateApis );
|
const { useHistory } = unlock( routerPrivateApis );
|
||||||
|
|
||||||
|
@ -66,6 +67,7 @@ export const BlockEditor = ( {} ) => {
|
||||||
const settings = useSiteEditorSettings();
|
const settings = useSiteEditorSettings();
|
||||||
const [ blocks ] = useEditorBlocks();
|
const [ blocks ] = useEditorBlocks();
|
||||||
const urlParams = useQuery();
|
const urlParams = useQuery();
|
||||||
|
const { currentState } = useContext( CustomizeStoreContext );
|
||||||
|
|
||||||
const scrollDirection =
|
const scrollDirection =
|
||||||
urlParams.path === '/customize-store/assembler-hub/footer'
|
urlParams.path === '/customize-store/assembler-hub/footer'
|
||||||
|
@ -124,6 +126,7 @@ export const BlockEditor = ( {} ) => {
|
||||||
settings={ settings }
|
settings={ settings }
|
||||||
additionalStyles={ '' }
|
additionalStyles={ '' }
|
||||||
isNavigable={ false }
|
isNavigable={ false }
|
||||||
|
isScrollable={ currentState !== 'transitionalScreen' }
|
||||||
onClickNavigationItem={ onClickNavigationItem }
|
onClickNavigationItem={ onClickNavigationItem }
|
||||||
// Don't use sub registry so that we can get the logo block from the main registry on the logo sidebar navigation screen component.
|
// Don't use sub registry so that we can get the logo block from the main registry on the logo sidebar navigation screen component.
|
||||||
useSubRegistry={ false }
|
useSubRegistry={ false }
|
||||||
|
|
|
@ -59,9 +59,11 @@ type CustomizeStoreComponentProps = Parameters< CustomizeStoreComponent >[ 0 ];
|
||||||
export const CustomizeStoreContext = createContext< {
|
export const CustomizeStoreContext = createContext< {
|
||||||
sendEvent: CustomizeStoreComponentProps[ 'sendEvent' ];
|
sendEvent: CustomizeStoreComponentProps[ 'sendEvent' ];
|
||||||
context: Partial< CustomizeStoreComponentProps[ 'context' ] >;
|
context: Partial< CustomizeStoreComponentProps[ 'context' ] >;
|
||||||
|
currentState: CustomizeStoreComponentProps[ 'currentState' ];
|
||||||
} >( {
|
} >( {
|
||||||
sendEvent: () => {},
|
sendEvent: () => {},
|
||||||
context: {},
|
context: {},
|
||||||
|
currentState: 'assemblerHub',
|
||||||
} );
|
} );
|
||||||
|
|
||||||
export type events =
|
export type events =
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
useViewportMatch,
|
useViewportMatch,
|
||||||
} from '@wordpress/compose';
|
} from '@wordpress/compose';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { useState } from '@wordpress/element';
|
import { useState, useContext } from '@wordpress/element';
|
||||||
import {
|
import {
|
||||||
// @ts-ignore No types for this exist yet.
|
// @ts-ignore No types for this exist yet.
|
||||||
__unstableMotion as motion,
|
__unstableMotion as motion,
|
||||||
|
@ -45,6 +45,8 @@ import { LogoBlockContext } from './logo-block-context';
|
||||||
import ResizableFrame from './resizable-frame';
|
import ResizableFrame from './resizable-frame';
|
||||||
import { OnboardingTour, useOnboardingTour } from './onboarding-tour';
|
import { OnboardingTour, useOnboardingTour } from './onboarding-tour';
|
||||||
import { HighlightedBlockContextProvider } from './context/highlighted-block-context';
|
import { HighlightedBlockContextProvider } from './context/highlighted-block-context';
|
||||||
|
import { Transitional } from '../transitional';
|
||||||
|
import { CustomizeStoreContext } from './';
|
||||||
|
|
||||||
const { useGlobalStyle } = unlock( blockEditorPrivateApis );
|
const { useGlobalStyle } = unlock( blockEditorPrivateApis );
|
||||||
|
|
||||||
|
@ -74,6 +76,24 @@ export const Layout = () => {
|
||||||
const { record: template } = useEditedEntityRecord();
|
const { record: template } = useEditedEntityRecord();
|
||||||
const { id: templateId, type: templateType } = template;
|
const { id: templateId, type: templateType } = template;
|
||||||
|
|
||||||
|
const { sendEvent, currentState } = useContext( CustomizeStoreContext );
|
||||||
|
|
||||||
|
const editor = <Editor isLoading={ isEditorLoading } />;
|
||||||
|
|
||||||
|
if ( currentState === 'transitionalScreen' ) {
|
||||||
|
return (
|
||||||
|
<EntityProvider kind="root" type="site">
|
||||||
|
<EntityProvider
|
||||||
|
kind="postType"
|
||||||
|
type={ templateType }
|
||||||
|
id={ templateId }
|
||||||
|
>
|
||||||
|
<Transitional sendEvent={ sendEvent } editor={ editor } />
|
||||||
|
</EntityProvider>
|
||||||
|
</EntityProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogoBlockContext.Provider
|
<LogoBlockContext.Provider
|
||||||
value={ {
|
value={ {
|
||||||
|
@ -188,11 +208,7 @@ export const Layout = () => {
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
<Editor
|
{ editor }
|
||||||
isLoading={
|
|
||||||
isEditorLoading
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ResizableFrame>
|
</ResizableFrame>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
|
@ -25,11 +25,7 @@ import {
|
||||||
} from './intro';
|
} from './intro';
|
||||||
import { DesignWithAi, events as designWithAiEvents } from './design-with-ai';
|
import { DesignWithAi, events as designWithAiEvents } from './design-with-ai';
|
||||||
import { AssemblerHub, events as assemblerHubEvents } from './assembler-hub';
|
import { AssemblerHub, events as assemblerHubEvents } from './assembler-hub';
|
||||||
import {
|
import { events as transitionalEvents } from './transitional';
|
||||||
Transitional,
|
|
||||||
events as transitionalEvents,
|
|
||||||
services as transitionalServices,
|
|
||||||
} from './transitional';
|
|
||||||
import { findComponentMeta } from '~/utils/xstate/find-component';
|
import { findComponentMeta } from '~/utils/xstate/find-component';
|
||||||
import {
|
import {
|
||||||
CustomizeStoreComponentMeta,
|
CustomizeStoreComponentMeta,
|
||||||
|
@ -102,7 +98,6 @@ export const customizeStoreStateMachineActions = {
|
||||||
|
|
||||||
export const customizeStoreStateMachineServices = {
|
export const customizeStoreStateMachineServices = {
|
||||||
...introServices,
|
...introServices,
|
||||||
...transitionalServices,
|
|
||||||
browserPopstateHandler,
|
browserPopstateHandler,
|
||||||
markTaskComplete,
|
markTaskComplete,
|
||||||
};
|
};
|
||||||
|
@ -264,14 +259,6 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
||||||
invoke: {
|
invoke: {
|
||||||
src: 'markTaskComplete',
|
src: 'markTaskComplete',
|
||||||
onDone: {
|
onDone: {
|
||||||
target: 'waitForSitePreview',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
waitForSitePreview: {
|
|
||||||
after: {
|
|
||||||
// Wait for 5 seconds before redirecting to the transitional page. This is to ensure that the site preview image is refreshed.
|
|
||||||
5000: {
|
|
||||||
target: '#customizeStore.transitionalScreen',
|
target: '#customizeStore.transitionalScreen',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -279,8 +266,6 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
FINISH_CUSTOMIZATION: {
|
FINISH_CUSTOMIZATION: {
|
||||||
// Pre-fetch the site preview image for the site for transitional page.
|
|
||||||
actions: [ 'prefetchSitePreview' ],
|
|
||||||
target: '.postAssemblerHub',
|
target: '.postAssemblerHub',
|
||||||
},
|
},
|
||||||
GO_BACK_TO_DESIGN_WITH_AI: {
|
GO_BACK_TO_DESIGN_WITH_AI: {
|
||||||
|
@ -291,7 +276,7 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
||||||
transitionalScreen: {
|
transitionalScreen: {
|
||||||
entry: [ { type: 'updateQueryStep', step: 'transitional' } ],
|
entry: [ { type: 'updateQueryStep', step: 'transitional' } ],
|
||||||
meta: {
|
meta: {
|
||||||
component: Transitional,
|
component: AssemblerHub,
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
GO_BACK_TO_HOME: {
|
GO_BACK_TO_HOME: {
|
||||||
|
@ -368,6 +353,7 @@ export const CustomizeStoreController = ( {
|
||||||
parentMachine={ service }
|
parentMachine={ service }
|
||||||
sendEvent={ send }
|
sendEvent={ send }
|
||||||
context={ state.context }
|
context={ state.context }
|
||||||
|
currentState={ state.value }
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
/* eslint-disable @woocommerce/dependency-group */
|
||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
|
import classNames from 'classnames';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { getSetting } from '@woocommerce/settings';
|
import { getSetting } from '@woocommerce/settings';
|
||||||
import { recordEvent } from '@woocommerce/tracks';
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
|
@ -10,31 +13,28 @@ import {
|
||||||
// @ts-ignore No types for this exist yet.
|
// @ts-ignore No types for this exist yet.
|
||||||
__unstableMotion as motion,
|
__unstableMotion as motion,
|
||||||
} from '@wordpress/components';
|
} from '@wordpress/components';
|
||||||
|
// @ts-ignore No types for this exist yet.
|
||||||
|
import { useIsSiteEditorLoading } from '@wordpress/edit-site/build-module/components/layout/hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { SiteHub } from '../assembler-hub/site-hub';
|
import { SiteHub } from '../assembler-hub/site-hub';
|
||||||
import { MShotsImage } from './mshots-image';
|
|
||||||
import { ADMIN_URL } from '~/utils/admin-settings';
|
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||||
|
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
export * as services from './services';
|
|
||||||
|
|
||||||
export type events = { type: 'GO_BACK_TO_HOME' };
|
export type events = { type: 'GO_BACK_TO_HOME' };
|
||||||
export const PREVIEW_IMAGE_OPTION = {
|
|
||||||
vpw: 1200,
|
|
||||||
vph: 742,
|
|
||||||
w: 588,
|
|
||||||
h: 363.58,
|
|
||||||
requeue: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Transitional = ( {
|
export const Transitional = ( {
|
||||||
|
editor,
|
||||||
sendEvent,
|
sendEvent,
|
||||||
}: {
|
}: {
|
||||||
|
editor: React.ReactNode;
|
||||||
sendEvent: ( event: events ) => void;
|
sendEvent: ( event: events ) => void;
|
||||||
} ) => {
|
} ) => {
|
||||||
const homeUrl: string = getSetting( 'homeUrl', '' );
|
const homeUrl: string = getSetting( 'homeUrl', '' );
|
||||||
|
const isEditorLoading = useIsSiteEditorLoading();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="woocommerce-customize-store__transitional">
|
<div className="woocommerce-customize-store__transitional">
|
||||||
|
@ -70,16 +70,15 @@ export const Transitional = ( {
|
||||||
{ __( 'Preview store', 'woocommerce' ) }
|
{ __( 'Preview store', 'woocommerce' ) }
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="woocommerce-customize-store__transitional-site-img-container">
|
<div
|
||||||
<MShotsImage
|
className={ classNames(
|
||||||
url={ homeUrl }
|
'woocommerce-customize-store__transitional-site-preview-container',
|
||||||
alt={ __( 'Your store screenshot', 'woocommerce' ) }
|
{
|
||||||
aria-labelledby={ __(
|
'is-loading': isEditorLoading,
|
||||||
'Your store screenshot',
|
}
|
||||||
'woocommerce'
|
) }
|
||||||
) }
|
>
|
||||||
options={ PREVIEW_IMAGE_OPTION }
|
{ editor }
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="woocommerce-customize-store__transitional-actions">
|
<div className="woocommerce-customize-store__transitional-actions">
|
||||||
<div className="woocommerce-customize-store__transitional-action">
|
<div className="woocommerce-customize-store__transitional-action">
|
||||||
|
|
|
@ -1,215 +0,0 @@
|
||||||
// See https://github.com/Automattic/wp-calypso/blob/6923bed938911a931e722a1efa6fcbbf942677a9/packages/onboarding/src/mshots-image/index.tsx
|
|
||||||
// TODO: @automattic/onboarding is not published to npm, so we can't use it here. We'll need to add the "requeue" option to MShotsOptions and we could remove this when it's published.
|
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import { addQueryArgs } from '@wordpress/url';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import { useState, useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
interface MShotsImageProps {
|
|
||||||
url: string;
|
|
||||||
alt: string;
|
|
||||||
'aria-labelledby': string;
|
|
||||||
options: MShotsOptions;
|
|
||||||
scrollable?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MShotsOptions = {
|
|
||||||
vpw: number;
|
|
||||||
vph: number;
|
|
||||||
w: number;
|
|
||||||
h?: number;
|
|
||||||
screen_height?: number;
|
|
||||||
format?: 'png' | 'jpeg';
|
|
||||||
requeue?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
const debug = console.debug;
|
|
||||||
|
|
||||||
export function getMshotsUrl(
|
|
||||||
targetUrl: string,
|
|
||||||
options: MShotsOptions,
|
|
||||||
count = 0
|
|
||||||
): string {
|
|
||||||
const mshotsUrl = 'https://s0.wp.com/mshots/v1/';
|
|
||||||
const mshotsRequest = addQueryArgs(
|
|
||||||
mshotsUrl + encodeURIComponent( targetUrl ),
|
|
||||||
{
|
|
||||||
...options,
|
|
||||||
count,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return mshotsRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAXTRIES = 10;
|
|
||||||
|
|
||||||
// This custom react hook returns undefined while the image is loading and
|
|
||||||
// a HTMLImageElement (i.e. the class you get from `new Image()`) once loading
|
|
||||||
// is complete.
|
|
||||||
//
|
|
||||||
// It also triggers a re-render (via setState()) when the value changes, so just
|
|
||||||
// check if it's truthy and then treat it like any other Image.
|
|
||||||
//
|
|
||||||
// Note the loading may occur immediately and synchronously if the image is
|
|
||||||
// already or may take up to several seconds if mshots has to generate and cache
|
|
||||||
// new images.
|
|
||||||
//
|
|
||||||
// The calling code doesn't need to worry about the details except that you'll
|
|
||||||
// want some sort of loading display.
|
|
||||||
//
|
|
||||||
// Inspired by https://stackoverflow.com/a/60458593
|
|
||||||
const useMshotsImg = (
|
|
||||||
src: string,
|
|
||||||
options: MShotsOptions
|
|
||||||
): HTMLImageElement | undefined => {
|
|
||||||
const [ loadedImg, setLoadedImg ] = useState< HTMLImageElement >();
|
|
||||||
const [ count, setCount ] = useState( 0 );
|
|
||||||
const previousSrc = useRef( src );
|
|
||||||
|
|
||||||
const imgRef = useRef< HTMLImageElement >();
|
|
||||||
const timeoutIdRef = useRef< number >();
|
|
||||||
|
|
||||||
const previousImg = useRef< HTMLImageElement >();
|
|
||||||
const previousOptions = useRef< MShotsOptions >();
|
|
||||||
// Oddly, we need to assign to current here after ref creation in order to
|
|
||||||
// pass the equivalence check and avoid a spurious reset
|
|
||||||
previousOptions.current = options;
|
|
||||||
|
|
||||||
// Note: Mshots doesn't care about the "count" param, but it is important
|
|
||||||
// to browser caching. Getting this wrong looks like the url resolving
|
|
||||||
// before the image is ready.
|
|
||||||
useEffect( () => {
|
|
||||||
// If there's been a "props" change we need to reset everything:
|
|
||||||
if (
|
|
||||||
options !== previousOptions.current ||
|
|
||||||
( src !== previousSrc.current && imgRef.current )
|
|
||||||
) {
|
|
||||||
// Make sure an old image can't trigger a spurious state update
|
|
||||||
debug( 'resetting mShotsUrl request' );
|
|
||||||
if ( src !== previousSrc.current ) {
|
|
||||||
debug( 'src changed\nfrom', previousSrc.current, '\nto', src );
|
|
||||||
}
|
|
||||||
if ( options !== previousOptions.current ) {
|
|
||||||
debug(
|
|
||||||
'options changed\nfrom',
|
|
||||||
previousOptions.current,
|
|
||||||
'\nto',
|
|
||||||
options
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ( previousImg.current && previousImg.current.onload ) {
|
|
||||||
previousImg.current.onload = null;
|
|
||||||
if ( timeoutIdRef.current ) {
|
|
||||||
clearTimeout( timeoutIdRef.current );
|
|
||||||
timeoutIdRef.current = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadedImg( undefined );
|
|
||||||
setCount( 0 );
|
|
||||||
previousImg.current = imgRef.current;
|
|
||||||
|
|
||||||
previousOptions.current = options;
|
|
||||||
previousSrc.current = src;
|
|
||||||
}
|
|
||||||
|
|
||||||
const srcUrl = getMshotsUrl( src, options, count );
|
|
||||||
const newImage = new Image();
|
|
||||||
newImage.onload = () => {
|
|
||||||
// Detect default image (Don't request a 400x300 image).
|
|
||||||
//
|
|
||||||
// If this turns out to be a problem, it might help to know that the
|
|
||||||
// http request status for the default is a 307. Unfortunately we
|
|
||||||
// don't get the request through an img element so we'd need to
|
|
||||||
// take a completely different approach using ajax.
|
|
||||||
if (
|
|
||||||
newImage.naturalWidth !== 400 ||
|
|
||||||
newImage.naturalHeight !== 300
|
|
||||||
) {
|
|
||||||
// Note we're using the naked object here, not the ref, because
|
|
||||||
// this is the callback on the image itself. We'd never want
|
|
||||||
// the image to finish loading and set some other image.
|
|
||||||
setLoadedImg( newImage );
|
|
||||||
} else if ( count < MAXTRIES ) {
|
|
||||||
// Only refresh 10 times
|
|
||||||
// Triggers a target.src change with increasing timeouts
|
|
||||||
timeoutIdRef.current = window.setTimeout(
|
|
||||||
() => setCount( ( _count ) => _count + 1 ),
|
|
||||||
count * 500
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
newImage.src = srcUrl;
|
|
||||||
imgRef.current = newImage;
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if ( imgRef.current && imgRef.current.onload ) {
|
|
||||||
imgRef.current.onload = null;
|
|
||||||
}
|
|
||||||
clearTimeout( timeoutIdRef.current );
|
|
||||||
};
|
|
||||||
}, [ src, count, options ] );
|
|
||||||
|
|
||||||
return loadedImg;
|
|
||||||
};
|
|
||||||
|
|
||||||
// For hover-scroll, we use a div with a background image (rather than an img element)
|
|
||||||
// in order to use transitions between `top` and `bottom` on the
|
|
||||||
// `background-position` property.
|
|
||||||
// The "normal" top & bottom properties are problematic individually because we
|
|
||||||
// don't know how big the images will be, and using both gets the
|
|
||||||
// right positions but with no transition (as they're different properties).
|
|
||||||
export const MShotsImage = ( {
|
|
||||||
url,
|
|
||||||
'aria-labelledby': labelledby,
|
|
||||||
alt,
|
|
||||||
options,
|
|
||||||
scrollable = false,
|
|
||||||
}: MShotsImageProps ) => {
|
|
||||||
const maybeImage = useMshotsImg( url, options );
|
|
||||||
const src: string = maybeImage?.src || '';
|
|
||||||
const visible = !! src;
|
|
||||||
const backgroundImage = maybeImage?.src && `url( ${ maybeImage?.src } )`;
|
|
||||||
|
|
||||||
const animationScrollSpeedInPixelsPerSecond = 400;
|
|
||||||
const animationDuration =
|
|
||||||
( maybeImage?.naturalHeight || 600 ) /
|
|
||||||
animationScrollSpeedInPixelsPerSecond;
|
|
||||||
|
|
||||||
const scrollableStyles = {
|
|
||||||
backgroundImage,
|
|
||||||
transition: `background-position ${ animationDuration }s`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
...( scrollable ? scrollableStyles : {} ),
|
|
||||||
};
|
|
||||||
|
|
||||||
const className = classnames(
|
|
||||||
'mshots-image__container',
|
|
||||||
scrollable && 'hover-scroll',
|
|
||||||
visible ? 'mshots-image-visible' : 'mshots-image__loader'
|
|
||||||
);
|
|
||||||
|
|
||||||
// The "! visible" here is only to dodge a particularly specific css
|
|
||||||
// rule effecting the placeholder while loading static images:
|
|
||||||
// '.design-picker .design-picker__image-frame img { ..., height: auto }'
|
|
||||||
return scrollable || ! visible ? (
|
|
||||||
<div
|
|
||||||
className={ className }
|
|
||||||
style={ style }
|
|
||||||
aria-labelledby={ labelledby }
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
{ ...{ className, style, src, alt } }
|
|
||||||
aria-labelledby={ labelledby }
|
|
||||||
alt={ alt }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MShotsImage;
|
|
|
@ -1,19 +0,0 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import { getSetting } from '@woocommerce/settings';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { getMshotsUrl } from './mshots-image';
|
|
||||||
import { PREVIEW_IMAGE_OPTION } from './';
|
|
||||||
|
|
||||||
export const fetchSitePreviewImage = async () => {
|
|
||||||
const homeUrl: string = getSetting( 'homeUrl', '' );
|
|
||||||
return window
|
|
||||||
.fetch( getMshotsUrl( homeUrl, PREVIEW_IMAGE_OPTION ) )
|
|
||||||
.catch( () => {
|
|
||||||
// Ignore errors
|
|
||||||
} );
|
|
||||||
};
|
|
|
@ -67,25 +67,59 @@
|
||||||
margin: 20px 0 0;
|
margin: 20px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.woocommerce-customize-store__transitional-site-img-container {
|
.woocommerce-customize-store__transitional-site-preview-container {
|
||||||
width: 600px;
|
|
||||||
height: 371px;
|
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
margin-top: 50px;
|
||||||
background: #f6f7f7;
|
background: #f6f7f7;
|
||||||
box-shadow: 0 6px 6px 0 rgba(0, 0, 0, 0.02), 0 13px 10px 0 rgba(0, 0, 0, 0.03), 0 15px 20px 0 rgba(0, 0, 0, 0.04);
|
box-shadow: 0 6px 6px 0 rgba(0, 0, 0, 0.02), 0 13px 10px 0 rgba(0, 0, 0, 0.03), 0 15px 20px 0 rgba(0, 0, 0, 0.04);
|
||||||
margin-top: 50px;
|
width: 600px;
|
||||||
display: flex;
|
height: 371px;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.mshots-image__loader {
|
div {
|
||||||
width: 600px;
|
position: relative;
|
||||||
height: 371px;
|
border-radius: 24px;
|
||||||
border-radius: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
.woocommerce-customize-store__edit-site-editor {
|
||||||
border-radius: 16px;
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-customize-store__block-editor {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interface-navigable-region {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-block-preview__container {
|
||||||
|
width: 588px;
|
||||||
|
height: 363px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border-radius: 24px;
|
||||||
|
width: 1176px;
|
||||||
|
height: 726px;
|
||||||
|
transform: scale(0.5);
|
||||||
|
left: -50%;
|
||||||
|
position: relative;
|
||||||
|
top: -50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-loading {
|
||||||
|
@include placeholder();
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,22 +167,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// mshots component
|
|
||||||
.mshots-image__loader {
|
|
||||||
@include placeholder();
|
|
||||||
}
|
|
||||||
|
|
||||||
.mshots-image-visible {
|
|
||||||
animation: fadein 300ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadein {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,13 +10,6 @@ import { recordEvent } from '@woocommerce/tracks';
|
||||||
*/
|
*/
|
||||||
import { Transitional } from '../index';
|
import { Transitional } from '../index';
|
||||||
|
|
||||||
jest.mock( '../mshots-image', () => ( {
|
|
||||||
__esModule: true,
|
|
||||||
MShotsImage: () => {
|
|
||||||
return <img alt="preview-img" />;
|
|
||||||
},
|
|
||||||
} ) );
|
|
||||||
|
|
||||||
jest.mock( '../../assembler-hub/site-hub', () => ( {
|
jest.mock( '../../assembler-hub/site-hub', () => ( {
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
SiteHub: () => {
|
SiteHub: () => {
|
||||||
|
@ -24,6 +17,14 @@ jest.mock( '../../assembler-hub/site-hub', () => ( {
|
||||||
},
|
},
|
||||||
} ) );
|
} ) );
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'@wordpress/edit-site/build-module/components/layout/hooks',
|
||||||
|
() => ( {
|
||||||
|
__esModule: true,
|
||||||
|
useIsSiteEditorLoading: jest.fn().mockReturnValue( false ),
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
|
||||||
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
|
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
|
||||||
|
|
||||||
describe( 'Transitional', () => {
|
describe( 'Transitional', () => {
|
||||||
|
@ -45,8 +46,6 @@ describe( 'Transitional', () => {
|
||||||
screen.getByText( /Your store looks great!/i )
|
screen.getByText( /Your store looks great!/i )
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
|
||||||
expect( screen.getByRole( 'img' ) ).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole( 'button', {
|
screen.getByRole( 'button', {
|
||||||
name: /Preview store/i,
|
name: /Preview store/i,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { AnyInterpreter, Sender } from 'xstate';
|
import { AnyInterpreter, Sender, StateValue } from 'xstate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -13,6 +13,7 @@ export type CustomizeStoreComponent = ( props: {
|
||||||
parentMachine: AnyInterpreter;
|
parentMachine: AnyInterpreter;
|
||||||
sendEvent: Sender< customizeStoreStateMachineEvents >;
|
sendEvent: Sender< customizeStoreStateMachineEvents >;
|
||||||
context: customizeStoreStateMachineContext;
|
context: customizeStoreStateMachineContext;
|
||||||
|
currentState: StateValue;
|
||||||
} ) => React.ReactElement | null;
|
} ) => React.ReactElement | null;
|
||||||
|
|
||||||
export type CustomizeStoreComponentMeta = {
|
export type CustomizeStoreComponentMeta = {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: fix
|
||||||
|
|
||||||
|
Fix customize your store site preview in transitional screen
|
Loading…
Reference in New Issue