Add customize store assembler hub (#39843)
* Add @wordpress dependencies for customize store task * Update webpack config to bundle wp edit-site package instead of using external * Add customize-store task list item fill * Update CustomizeStore task to load editor scripts and settings * Update customize store routing path Use /* since we want to match any path that starts with customize-store * Add assembler-hub * Ignore some wp packages from syncpack for customize store assembler hub We need to use specific versions of these packages for the customize store "@wordpress/interface", "@wordpress/router", "@wordpress/edit-site" * Add changefile(s) from automation for the following project(s): woocommerce * Tweak style * Use CustomizeStoreContext and send xstate event * Update assembler-hub style * Fix nav width --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
parent
4dc745cc37
commit
56f4ad623f
|
@ -112,7 +112,10 @@
|
|||
{
|
||||
"dependencies": [
|
||||
"@wordpress/block**",
|
||||
"@wordpress/viewport"
|
||||
"@wordpress/viewport",
|
||||
"@wordpress/interface",
|
||||
"@wordpress/router",
|
||||
"@wordpress/edit-site"
|
||||
],
|
||||
"packages": [
|
||||
"@woocommerce/product-editor",
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
// Reference: https://github.com/WordPress/gutenberg/blob/release/16.4/packages/block-editor/src/components/block-preview/auto.js
|
||||
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useResizeObserver, pure, useRefEffect } from '@wordpress/compose';
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import {
|
||||
__unstableEditorStyles as EditorStyles,
|
||||
__unstableIframe as Iframe,
|
||||
BlockList,
|
||||
MemoizedBlockList,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
} from '@wordpress/block-editor';
|
||||
|
||||
const MAX_HEIGHT = 2000;
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { Provider: DisabledProvider } = Disabled.Context;
|
||||
|
||||
export type ScaledBlockPreviewProps = {
|
||||
viewportWidth?: number;
|
||||
containerWidth: number;
|
||||
minHeight?: number;
|
||||
settings: {
|
||||
styles: string[];
|
||||
[ key: string ]: unknown;
|
||||
};
|
||||
additionalStyles: string;
|
||||
onClickNavigationItem: ( event: MouseEvent ) => void;
|
||||
};
|
||||
|
||||
function ScaledBlockPreview( {
|
||||
viewportWidth,
|
||||
containerWidth,
|
||||
minHeight,
|
||||
settings,
|
||||
additionalStyles,
|
||||
onClickNavigationItem,
|
||||
}: ScaledBlockPreviewProps ) {
|
||||
if ( ! viewportWidth ) {
|
||||
viewportWidth = containerWidth;
|
||||
}
|
||||
|
||||
const [ contentResizeListener, { height: contentHeight } ] =
|
||||
useResizeObserver();
|
||||
|
||||
// Avoid scrollbars for pattern previews.
|
||||
const editorStyles = useMemo( () => {
|
||||
return [
|
||||
{
|
||||
css: 'body{height:auto;overflow:hidden;border:none;padding:0;}',
|
||||
__unstableType: 'presets',
|
||||
},
|
||||
...settings.styles,
|
||||
];
|
||||
}, [ settings.styles ] );
|
||||
|
||||
// Initialize on render instead of module top level, to avoid circular dependency issues.
|
||||
const RenderedBlockList = MemoizedBlockList || pure( BlockList );
|
||||
const scale = containerWidth / viewportWidth;
|
||||
|
||||
return (
|
||||
<DisabledProvider value={ true }>
|
||||
<Iframe
|
||||
contentRef={ useRefEffect( ( bodyElement: HTMLBodyElement ) => {
|
||||
const {
|
||||
ownerDocument: { documentElement },
|
||||
} = bodyElement;
|
||||
|
||||
documentElement.classList.add(
|
||||
'block-editor-block-preview__content-iframe'
|
||||
);
|
||||
documentElement.style.position = 'absolute';
|
||||
documentElement.style.width = '100%';
|
||||
|
||||
// Necessary for contentResizeListener to work.
|
||||
bodyElement.style.boxSizing = 'border-box';
|
||||
bodyElement.style.position = 'absolute';
|
||||
bodyElement.style.width = '100%';
|
||||
|
||||
let navigationContainers: NodeListOf< HTMLDivElement >;
|
||||
let siteTitles: NodeListOf< HTMLAnchorElement >;
|
||||
const onClickNavigation = ( event: MouseEvent ) => {
|
||||
event.preventDefault();
|
||||
onClickNavigationItem( event );
|
||||
};
|
||||
|
||||
const onMouseMove = ( event: MouseEvent ) => {
|
||||
event.stopImmediatePropagation();
|
||||
};
|
||||
|
||||
const possiblyRemoveAllListeners = () => {
|
||||
bodyElement.removeEventListener(
|
||||
'mousemove',
|
||||
onMouseMove,
|
||||
false
|
||||
);
|
||||
if ( navigationContainers ) {
|
||||
navigationContainers.forEach( ( element ) => {
|
||||
element.removeEventListener(
|
||||
'click',
|
||||
onClickNavigation
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
if ( siteTitles ) {
|
||||
siteTitles.forEach( ( element ) => {
|
||||
element.removeEventListener(
|
||||
'click',
|
||||
onClickNavigation
|
||||
);
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
// Remove contenteditable and inert attributes from editable elements so that users can click on navigation links.
|
||||
bodyElement
|
||||
.querySelectorAll(
|
||||
'.block-editor-rich-text__editable[contenteditable="true"]'
|
||||
)
|
||||
.forEach( ( element ) => {
|
||||
element.removeAttribute( 'contenteditable' );
|
||||
} );
|
||||
|
||||
bodyElement
|
||||
.querySelectorAll( '*[inert="true"]' )
|
||||
.forEach( ( element ) => {
|
||||
element.removeAttribute( 'inert' );
|
||||
} );
|
||||
|
||||
possiblyRemoveAllListeners();
|
||||
navigationContainers = bodyElement.querySelectorAll(
|
||||
'.wp-block-navigation__container'
|
||||
);
|
||||
navigationContainers.forEach( ( element ) => {
|
||||
element.addEventListener(
|
||||
'click',
|
||||
onClickNavigation,
|
||||
true
|
||||
);
|
||||
} );
|
||||
|
||||
siteTitles = bodyElement.querySelectorAll(
|
||||
'.wp-block-site-title a'
|
||||
);
|
||||
siteTitles.forEach( ( element ) => {
|
||||
element.addEventListener(
|
||||
'click',
|
||||
onClickNavigation,
|
||||
true
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
// Stop mousemove event listener to disable block tool insertion feature.
|
||||
bodyElement.addEventListener(
|
||||
'mousemove',
|
||||
onMouseMove,
|
||||
true
|
||||
);
|
||||
|
||||
const observer = new window.MutationObserver( onChange );
|
||||
|
||||
observer.observe( bodyElement, {
|
||||
attributes: true,
|
||||
characterData: false,
|
||||
subtree: true,
|
||||
childList: true,
|
||||
} );
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
possiblyRemoveAllListeners();
|
||||
};
|
||||
}, [] ) }
|
||||
aria-hidden
|
||||
tabIndex={ -1 }
|
||||
style={ {
|
||||
width: viewportWidth,
|
||||
height: contentHeight,
|
||||
// This is a catch-all max-height for patterns.
|
||||
// Reference: https://github.com/WordPress/gutenberg/pull/38175.
|
||||
maxHeight: MAX_HEIGHT,
|
||||
minHeight:
|
||||
scale !== 0 && scale < 1 && minHeight
|
||||
? minHeight / scale
|
||||
: minHeight,
|
||||
} }
|
||||
>
|
||||
<EditorStyles styles={ editorStyles } />
|
||||
<style>
|
||||
{ `
|
||||
.block-editor-block-list__block::before,
|
||||
.is-selected::after,
|
||||
.is-hovered::after,
|
||||
.block-list-appender {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.block-editor-block-list__block.is-selected {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.block-editor-rich-text__editable {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.wp-block-site-title .block-editor-rich-text__editable {
|
||||
pointer-events: all !important;
|
||||
}
|
||||
|
||||
.wp-block-navigation .wp-block-pages-list__item__link {
|
||||
pointer-events: all !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
${ additionalStyles }
|
||||
` }
|
||||
</style>
|
||||
{ contentResizeListener }
|
||||
<RenderedBlockList renderAppender={ false } />
|
||||
</Iframe>
|
||||
</DisabledProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export const AutoHeightBlockPreview = (
|
||||
props: Omit< ScaledBlockPreviewProps, 'containerWidth' >
|
||||
) => {
|
||||
const [ containerResizeListener, { width: containerWidth } ] =
|
||||
useResizeObserver();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={ { position: 'relative', width: '100%', height: 0 } }>
|
||||
{ containerResizeListener }
|
||||
</div>
|
||||
<div className="auto-block-preview__container">
|
||||
{ !! containerWidth && (
|
||||
<ScaledBlockPreview
|
||||
{ ...props }
|
||||
containerWidth={ containerWidth }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,138 @@
|
|||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classNames from 'classnames';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { useEntityRecords, useEntityBlockEditor } from '@wordpress/core-data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as editSiteStore } from '@wordpress/edit-site/build-module/store';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import useSiteEditorSettings from '@wordpress/edit-site/build-module/components/block-editor/use-site-editor-settings';
|
||||
import { BlockInstance } from '@wordpress/blocks';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import BlockPreview from './block-preview';
|
||||
|
||||
const { useHistory, useLocation } = unlock( routerPrivateApis );
|
||||
|
||||
type Page = {
|
||||
link: string;
|
||||
title: { rendered: string; raw: string };
|
||||
[ key: string ]: unknown;
|
||||
};
|
||||
|
||||
// We only show the edit option when page count is <= MAX_PAGE_COUNT
|
||||
// Performance of Navigation Links is not good past this value.
|
||||
const MAX_PAGE_COUNT = 100;
|
||||
|
||||
export const BlockEditor = ( {} ) => {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const settings = useSiteEditorSettings();
|
||||
|
||||
const { templateType } = useSelect( ( select ) => {
|
||||
const { getEditedPostType } = unlock( select( editSiteStore ) );
|
||||
|
||||
return {
|
||||
templateType: getEditedPostType(),
|
||||
};
|
||||
}, [] );
|
||||
|
||||
const [ blocks ]: [ BlockInstance[] ] = useEntityBlockEditor(
|
||||
'postType',
|
||||
templateType
|
||||
);
|
||||
|
||||
// // See packages/block-library/src/page-list/edit.js.
|
||||
const { records: pages } = useEntityRecords( 'postType', 'page', {
|
||||
per_page: MAX_PAGE_COUNT,
|
||||
_fields: [ 'id', 'link', 'menu_order', 'parent', 'title', 'type' ],
|
||||
// TODO: When https://core.trac.wordpress.org/ticket/39037 REST API support for multiple orderby
|
||||
// values is resolved, update 'orderby' to [ 'menu_order', 'post_title' ] to provide a consistent
|
||||
// sort.
|
||||
orderby: 'menu_order',
|
||||
order: 'asc',
|
||||
} );
|
||||
|
||||
const onClickNavigationItem = ( event: MouseEvent ) => {
|
||||
const clickedPage =
|
||||
pages.find(
|
||||
( page: Page ) =>
|
||||
page.link === ( event.target as HTMLAnchorElement ).href
|
||||
) ||
|
||||
// Fallback to page title if the link is not found. This is needed for a bug in the block library
|
||||
// See https://github.com/woocommerce/team-ghidorah/issues/253#issuecomment-1665106817
|
||||
pages.find(
|
||||
( page: Page ) =>
|
||||
page.title.rendered ===
|
||||
( event.target as HTMLAnchorElement ).innerText
|
||||
);
|
||||
if ( clickedPage ) {
|
||||
history.push( {
|
||||
...location.params,
|
||||
postId: clickedPage.id,
|
||||
postType: 'page',
|
||||
} );
|
||||
} else {
|
||||
// Home page
|
||||
const { postId, postType, ...params } = location.params;
|
||||
history.push( {
|
||||
...params,
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="woocommerce-customize-store__block-editor">
|
||||
{ blocks.map( ( block, index ) => {
|
||||
// Add padding to the top and bottom of the block preview.
|
||||
let additionalStyles = '';
|
||||
let hasActionBar = false;
|
||||
switch ( true ) {
|
||||
case index === 0:
|
||||
// header
|
||||
additionalStyles = `
|
||||
.editor-styles-wrapper{ padding-top: var(--wp--style--root--padding-top) };'
|
||||
`;
|
||||
break;
|
||||
|
||||
case index === blocks.length - 1:
|
||||
// footer
|
||||
additionalStyles = `
|
||||
.editor-styles-wrapper{ padding-bottom: var(--wp--style--root--padding-bottom) };
|
||||
`;
|
||||
break;
|
||||
default:
|
||||
hasActionBar = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={ block.clientId }
|
||||
className={ classNames(
|
||||
'woocommerce-block-preview-container',
|
||||
{
|
||||
'has-action-menu': hasActionBar,
|
||||
}
|
||||
) }
|
||||
>
|
||||
<BlockPreview
|
||||
blocks={ block }
|
||||
settings={ settings }
|
||||
additionalStyles={ additionalStyles }
|
||||
onClickNavigationItem={ onClickNavigationItem }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} ) }
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
// Reference: https://github.com/WordPress/gutenberg/blob/release/16.4/packages/block-editor/src/components/block-preview/index.js
|
||||
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { BlockEditorProvider } from '@wordpress/block-editor';
|
||||
import { memo, useMemo } from '@wordpress/element';
|
||||
import { BlockInstance } from '@wordpress/blocks';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
AutoHeightBlockPreview,
|
||||
ScaledBlockPreviewProps,
|
||||
} from './auto-block-preview';
|
||||
|
||||
export const BlockPreview = ( {
|
||||
blocks,
|
||||
settings,
|
||||
...props
|
||||
}: {
|
||||
blocks: BlockInstance | BlockInstance[];
|
||||
settings: Record< string, unknown >;
|
||||
} & Omit< ScaledBlockPreviewProps, 'containerWidth' > ) => {
|
||||
const renderedBlocks = useMemo(
|
||||
() => ( Array.isArray( blocks ) ? blocks : [ blocks ] ),
|
||||
[ blocks ]
|
||||
);
|
||||
|
||||
return (
|
||||
<BlockEditorProvider value={ renderedBlocks } settings={ settings }>
|
||||
<AutoHeightBlockPreview settings={ settings } { ...props } />
|
||||
</BlockEditorProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo( BlockPreview );
|
|
@ -0,0 +1,100 @@
|
|||
// Reference: https://github.com/WordPress/gutenberg/tree/v16.4.0/packages/edit-site/src/components/editor/index.js
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { useMemo } from '@wordpress/element';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { EntityProvider } from '@wordpress/core-data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { InterfaceSkeleton } from '@wordpress/interface';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { BlockContextProvider } from '@wordpress/block-editor';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as editSiteStore } from '@wordpress/edit-site/build-module/store';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import CanvasSpinner from '@wordpress/edit-site/build-module/components/canvas-spinner';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import useEditedEntityRecord from '@wordpress/edit-site/build-module/components/use-edited-entity-record';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { GlobalStylesRenderer } from '@wordpress/edit-site/build-module/components/global-styles-renderer';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { BlockEditor } from './block-editor';
|
||||
|
||||
export const Editor = ( { isLoading }: { isLoading: boolean } ) => {
|
||||
const { record: template } = useEditedEntityRecord();
|
||||
const { id: templateId, type: templateType } = template;
|
||||
const { context, hasPageContentFocus } = useSelect( ( select ) => {
|
||||
const {
|
||||
getEditedPostContext,
|
||||
hasPageContentFocus: _hasPageContentFocus,
|
||||
} = unlock( select( editSiteStore ) );
|
||||
|
||||
// The currently selected entity to display.
|
||||
// Typically template or template part in the site editor.
|
||||
return {
|
||||
context: getEditedPostContext(),
|
||||
hasPageContentFocus: _hasPageContentFocus,
|
||||
};
|
||||
}, [] );
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { setEditedPostContext } = useDispatch( editSiteStore );
|
||||
const blockContext = useMemo( () => {
|
||||
const { postType, postId, ...nonPostFields } = context ?? {};
|
||||
return {
|
||||
...( hasPageContentFocus ? context : nonPostFields ),
|
||||
queryContext: [
|
||||
context?.queryContext || { page: 1 },
|
||||
( newQueryContext: Record< string, unknown > ) =>
|
||||
setEditedPostContext( {
|
||||
...context,
|
||||
queryContext: {
|
||||
...context?.queryContext,
|
||||
...newQueryContext,
|
||||
},
|
||||
} ),
|
||||
],
|
||||
};
|
||||
}, [ hasPageContentFocus, context, setEditedPostContext ] );
|
||||
|
||||
return (
|
||||
<>
|
||||
{ isLoading ? <CanvasSpinner /> : null }
|
||||
<EntityProvider kind="root" type="site">
|
||||
<EntityProvider
|
||||
kind="postType"
|
||||
type={ templateType }
|
||||
id={ templateId }
|
||||
>
|
||||
<BlockContextProvider value={ blockContext }>
|
||||
<InterfaceSkeleton
|
||||
enableRegionNavigation={ false }
|
||||
className={ classnames(
|
||||
'woocommerce-customize-store__edit-site-editor',
|
||||
'edit-site-editor__interface-skeleton',
|
||||
{
|
||||
'show-icon-labels': false,
|
||||
'is-loading': isLoading,
|
||||
}
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<GlobalStylesRenderer />
|
||||
<BlockEditor />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</BlockContextProvider>
|
||||
</EntityProvider>
|
||||
</EntityProvider>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1 +1,142 @@
|
|||
export type events = { type: 'FINISH_CUSTOMIZATION' };
|
||||
// Reference: https://github.com/WordPress/gutenberg/tree/v16.4.0/packages/edit-site/src/index.js
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect, createContext } from '@wordpress/element';
|
||||
import { dispatch, useDispatch } from '@wordpress/data';
|
||||
import {
|
||||
__experimentalFetchLinkSuggestions as fetchLinkSuggestions,
|
||||
__experimentalFetchUrlData as fetchUrlData,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
} from '@wordpress/core-data';
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import {
|
||||
registerCoreBlocks,
|
||||
__experimentalGetCoreBlocks,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
} from '@wordpress/block-library';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { getBlockType, store as blocksStore } from '@wordpress/blocks';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { ShortcutProvider } from '@wordpress/keyboard-shortcuts';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as preferencesStore } from '@wordpress/preferences';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as editorStore } from '@wordpress/editor';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as editSiteStore } from '@wordpress/edit-site/build-module/store';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { GlobalStylesProvider } from '@wordpress/edit-site/build-module/components/global-styles/global-styles-provider';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomizeStoreComponent } from '../types';
|
||||
import { Layout } from './layout';
|
||||
import './style.scss';
|
||||
|
||||
const { RouterProvider } = unlock( routerPrivateApis );
|
||||
|
||||
type CustomizeStoreComponentProps = Parameters< CustomizeStoreComponent >[ 0 ];
|
||||
|
||||
export const CustomizeStoreContext = createContext< {
|
||||
sendEvent: CustomizeStoreComponentProps[ 'sendEvent' ];
|
||||
context: Partial< CustomizeStoreComponentProps[ 'context' ] >;
|
||||
} >( {
|
||||
sendEvent: () => {},
|
||||
context: {},
|
||||
} );
|
||||
|
||||
export type events =
|
||||
| { type: 'FINISH_CUSTOMIZATION' }
|
||||
| { type: 'GO_BACK_TO_DESIGN_WITH_AI' };
|
||||
|
||||
export const AssemblerHub: CustomizeStoreComponent = ( props ) => {
|
||||
const { setCanvasMode } = unlock( useDispatch( editSiteStore ) );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! window.wcBlockSettings ) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'window.blockSettings not found. Skipping initialization.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the block editor settings.
|
||||
const settings = window.wcBlockSettings;
|
||||
settings.__experimentalFetchLinkSuggestions = (
|
||||
search: string,
|
||||
searchOptions: {
|
||||
isInitialSuggestions: boolean;
|
||||
type: 'attachment' | 'post' | 'term' | 'post-format';
|
||||
subtype: string;
|
||||
page: number;
|
||||
perPage: number;
|
||||
}
|
||||
) => fetchLinkSuggestions( search, searchOptions, settings );
|
||||
settings.__experimentalFetchRichUrlData = fetchUrlData;
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
dispatch( blocksStore ).__experimentalReapplyBlockTypeFilters();
|
||||
const coreBlocks = __experimentalGetCoreBlocks().filter(
|
||||
( { name }: { name: string } ) =>
|
||||
name !== 'core/freeform' && ! getBlockType( name )
|
||||
);
|
||||
registerCoreBlocks( coreBlocks );
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
dispatch( blocksStore ).setFreeformFallbackBlockName( 'core/html' );
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
dispatch( preferencesStore ).setDefaults( 'core/edit-site', {
|
||||
editorMode: 'visual',
|
||||
fixedToolbar: false,
|
||||
focusMode: false,
|
||||
distractionFree: false,
|
||||
keepCaretInsideBlock: false,
|
||||
welcomeGuide: false,
|
||||
welcomeGuideStyles: false,
|
||||
welcomeGuidePage: false,
|
||||
welcomeGuideTemplate: false,
|
||||
showListViewByDefault: false,
|
||||
showBlockBreadcrumbs: true,
|
||||
} );
|
||||
// @ts-ignore No types for this exist yet.
|
||||
dispatch( editSiteStore ).updateSettings( settings );
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
dispatch( editorStore ).updateEditorSettings( {
|
||||
defaultTemplateTypes: settings.defaultTemplateTypes,
|
||||
defaultTemplatePartAreas: settings.defaultTemplatePartAreas,
|
||||
} );
|
||||
|
||||
// Prevent the default browser action for files dropped outside of dropzones.
|
||||
window.addEventListener(
|
||||
'dragover',
|
||||
( e ) => e.preventDefault(),
|
||||
false
|
||||
);
|
||||
window.addEventListener( 'drop', ( e ) => e.preventDefault(), false );
|
||||
|
||||
setCanvasMode( 'view' );
|
||||
}, [ setCanvasMode ] );
|
||||
|
||||
return (
|
||||
<CustomizeStoreContext.Provider value={ props }>
|
||||
<ShortcutProvider style={ { height: '100%' } }>
|
||||
<GlobalStylesProvider>
|
||||
<RouterProvider>
|
||||
<Layout />
|
||||
</RouterProvider>
|
||||
</GlobalStylesProvider>
|
||||
</ShortcutProvider>
|
||||
</CustomizeStoreContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
// Reference: https://github.com/WordPress/gutenberg/tree/v16.4.0/packages/edit-site/src/components/layout/index.js
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { useState } from '@wordpress/element';
|
||||
import {
|
||||
useReducedMotion,
|
||||
useResizeObserver,
|
||||
useViewportMatch,
|
||||
} from '@wordpress/compose';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__unstableMotion as motion,
|
||||
} from '@wordpress/components';
|
||||
import {
|
||||
privateApis as blockEditorPrivateApis,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
} from '@wordpress/block-editor';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import ResizableFrame from '@wordpress/edit-site/build-module/components/resizable-frame';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import useInitEditedEntityFromURL from '@wordpress/edit-site/build-module/components/sync-state-with-url/use-init-edited-entity-from-url';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { useIsSiteEditorLoading } from '@wordpress/edit-site/build-module/components/layout/hooks';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import ErrorBoundary from '@wordpress/edit-site/build-module/components/error-boundary';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { NavigableRegion } from '@wordpress/interface';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Editor } from './editor';
|
||||
import Sidebar from './sidebar';
|
||||
import { SiteHub } from './site-hub';
|
||||
|
||||
const { useGlobalStyle } = unlock( blockEditorPrivateApis );
|
||||
|
||||
const ANIMATION_DURATION = 0.5;
|
||||
|
||||
export const Layout = () => {
|
||||
// This ensures the edited entity id and type are initialized properly.
|
||||
useInitEditedEntityFromURL();
|
||||
|
||||
const isMobileViewport = useViewportMatch( 'medium', '<' );
|
||||
const disableMotion = useReducedMotion();
|
||||
const [ canvasResizer, canvasSize ] = useResizeObserver();
|
||||
const isEditorLoading = useIsSiteEditorLoading();
|
||||
const [ isResizableFrameOversized, setIsResizableFrameOversized ] =
|
||||
useState( false );
|
||||
const [ backgroundColor ] = useGlobalStyle( 'color.background' );
|
||||
const [ gradientValue ] = useGlobalStyle( 'color.gradient' );
|
||||
|
||||
return (
|
||||
<div className={ classnames( 'edit-site-layout' ) }>
|
||||
<motion.div
|
||||
className="edit-site-layout__header-container"
|
||||
animate={ 'view' }
|
||||
>
|
||||
<SiteHub
|
||||
as={ motion.div }
|
||||
variants={ {
|
||||
view: { x: 0 },
|
||||
} }
|
||||
isTransparent={ isResizableFrameOversized }
|
||||
className="edit-site-layout__hub"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<div className="edit-site-layout__content">
|
||||
<NavigableRegion
|
||||
ariaLabel={ __( 'Navigation', 'woocommerce' ) }
|
||||
className="edit-site-layout__sidebar-region"
|
||||
>
|
||||
<motion.div
|
||||
animate={ { opacity: 1 } }
|
||||
transition={ {
|
||||
type: 'tween',
|
||||
duration:
|
||||
// Disable transitiont in mobile to emulate a full page transition.
|
||||
disableMotion || isMobileViewport
|
||||
? 0
|
||||
: ANIMATION_DURATION,
|
||||
ease: 'easeOut',
|
||||
} }
|
||||
className="edit-site-layout__sidebar"
|
||||
>
|
||||
<Sidebar />
|
||||
</motion.div>
|
||||
</NavigableRegion>
|
||||
|
||||
{ ! isMobileViewport && (
|
||||
<div
|
||||
className={ classnames(
|
||||
'edit-site-layout__canvas-container'
|
||||
) }
|
||||
>
|
||||
{ canvasResizer }
|
||||
{ !! canvasSize.width && (
|
||||
<motion.div
|
||||
whileHover={ {
|
||||
scale: 1.005,
|
||||
transition: {
|
||||
duration: disableMotion ? 0 : 0.5,
|
||||
ease: 'easeOut',
|
||||
},
|
||||
} }
|
||||
initial={ false }
|
||||
layout="position"
|
||||
className={ classnames(
|
||||
'edit-site-layout__canvas',
|
||||
{
|
||||
'is-right-aligned':
|
||||
isResizableFrameOversized,
|
||||
}
|
||||
) }
|
||||
transition={ {
|
||||
type: 'tween',
|
||||
duration: disableMotion
|
||||
? 0
|
||||
: ANIMATION_DURATION,
|
||||
ease: 'easeOut',
|
||||
} }
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<ResizableFrame
|
||||
isReady={ ! isEditorLoading }
|
||||
isFullWidth={ false }
|
||||
defaultSize={ {
|
||||
width:
|
||||
canvasSize.width -
|
||||
24 /* $canvas-padding */,
|
||||
height: canvasSize.height,
|
||||
} }
|
||||
isOversized={
|
||||
isResizableFrameOversized
|
||||
}
|
||||
setIsOversized={
|
||||
setIsResizableFrameOversized
|
||||
}
|
||||
innerContentStyle={ {
|
||||
background:
|
||||
gradientValue ??
|
||||
backgroundColor,
|
||||
} }
|
||||
>
|
||||
<Editor isLoading={ isEditorLoading } />
|
||||
</ResizableFrame>
|
||||
</ErrorBoundary>
|
||||
</motion.div>
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,144 @@
|
|||
// Reference: https://github.com/WordPress/gutenberg/blob/v16.4.0/packages/edit-site/src/components/sidebar/index.js
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { memo, useRef, useEffect } from '@wordpress/element';
|
||||
import {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalNavigatorProvider as NavigatorProvider,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalNavigatorScreen as NavigatorScreen,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalUseNavigator as useNavigator,
|
||||
} from '@wordpress/components';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreenMain } from './sidebar-navigation-screen-main';
|
||||
import { SidebarNavigationScreenColorPalette } from './sidebar-navigation-screen-color-palette';
|
||||
import { SidebarNavigationScreenTypography } from './sidebar-navigation-screen-typography';
|
||||
import { SidebarNavigationScreenHeader } from './sidebar-navigation-screen-header';
|
||||
import { SidebarNavigationScreenHomepage } from './sidebar-navigation-screen-homepage';
|
||||
import { SidebarNavigationScreenFooter } from './sidebar-navigation-screen-footer';
|
||||
import { SidebarNavigationScreenPages } from './sidebar-navigation-screen-pages';
|
||||
import { SidebarNavigationScreenLogo } from './sidebar-navigation-screen-logo';
|
||||
|
||||
import { SaveHub } from './save-hub';
|
||||
|
||||
const { useLocation, useHistory } = unlock( routerPrivateApis );
|
||||
|
||||
function isSubset(
|
||||
subset: {
|
||||
[ key: string ]: string | undefined;
|
||||
},
|
||||
superset: {
|
||||
[ key: string ]: string | undefined;
|
||||
}
|
||||
) {
|
||||
return Object.entries( subset ).every( ( [ key, value ] ) => {
|
||||
return superset[ key ] === value;
|
||||
} );
|
||||
}
|
||||
|
||||
function useSyncPathWithURL() {
|
||||
const history = useHistory();
|
||||
const { params: urlParams } = useLocation();
|
||||
const { location: navigatorLocation, params: navigatorParams } =
|
||||
useNavigator();
|
||||
const isMounting = useRef( true );
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
// The navigatorParams are only initially filled properly when the
|
||||
// navigator screens mount. so we ignore the first synchronisation.
|
||||
if ( isMounting.current ) {
|
||||
isMounting.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
function updateUrlParams( newUrlParams: {
|
||||
[ key: string ]: string | undefined;
|
||||
} ) {
|
||||
if ( isSubset( newUrlParams, urlParams ) ) {
|
||||
return;
|
||||
}
|
||||
const updatedParams = {
|
||||
...urlParams,
|
||||
...newUrlParams,
|
||||
};
|
||||
history.push( updatedParams );
|
||||
}
|
||||
|
||||
updateUrlParams( {
|
||||
postType: undefined,
|
||||
postId: undefined,
|
||||
categoryType: undefined,
|
||||
categoryId: undefined,
|
||||
path:
|
||||
navigatorLocation.path === '/'
|
||||
? undefined
|
||||
: navigatorLocation.path,
|
||||
} );
|
||||
},
|
||||
// Trigger only when navigator changes to prevent infinite loops.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[ navigatorLocation?.path, navigatorParams ]
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarScreens() {
|
||||
useSyncPathWithURL();
|
||||
return (
|
||||
<>
|
||||
<NavigatorScreen path="/customize-store">
|
||||
<SidebarNavigationScreenMain />
|
||||
</NavigatorScreen>
|
||||
<NavigatorScreen path="/customize-store/color-palette">
|
||||
<SidebarNavigationScreenColorPalette />
|
||||
</NavigatorScreen>
|
||||
<NavigatorScreen path="/customize-store/typography">
|
||||
<SidebarNavigationScreenTypography />
|
||||
</NavigatorScreen>
|
||||
<NavigatorScreen path="/customize-store/header">
|
||||
<SidebarNavigationScreenHeader />
|
||||
</NavigatorScreen>
|
||||
<NavigatorScreen path="/customize-store/homepage">
|
||||
<SidebarNavigationScreenHomepage />
|
||||
</NavigatorScreen>
|
||||
<NavigatorScreen path="/customize-store/footer">
|
||||
<SidebarNavigationScreenFooter />
|
||||
</NavigatorScreen>
|
||||
<NavigatorScreen path="/customize-store/pages">
|
||||
<SidebarNavigationScreenPages />
|
||||
</NavigatorScreen>
|
||||
<NavigatorScreen path="/customize-store/logo">
|
||||
<SidebarNavigationScreenLogo />
|
||||
</NavigatorScreen>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Sidebar() {
|
||||
const { params: urlParams } = useLocation();
|
||||
const initialPath = useRef( urlParams.path ?? '/customize-store' );
|
||||
return (
|
||||
<>
|
||||
<NavigatorProvider
|
||||
className="edit-site-sidebar__content"
|
||||
initialPath={ initialPath.current }
|
||||
>
|
||||
<SidebarScreens />
|
||||
</NavigatorProvider>
|
||||
<SaveHub />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo( Sidebar );
|
|
@ -0,0 +1,228 @@
|
|||
// Reference: https://github.com/WordPress/gutenberg/blob/v16.4.0/packages/edit-site/src/components/save-hub/index.js
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useContext } from '@wordpress/element';
|
||||
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { Button, __experimentalHStack as HStack } from '@wordpress/components';
|
||||
import { __, sprintf, _n } from '@wordpress/i18n';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as blockEditorStore } from '@wordpress/block-editor';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { check } from '@wordpress/icons';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import SaveButton from '@wordpress/edit-site/build-module/components/save-button';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomizeStoreContext } from '../';
|
||||
|
||||
const { useLocation } = unlock( routerPrivateApis );
|
||||
|
||||
const PUBLISH_ON_SAVE_ENTITIES = [
|
||||
{
|
||||
kind: 'postType',
|
||||
name: 'wp_navigation',
|
||||
},
|
||||
];
|
||||
|
||||
export const SaveHub = () => {
|
||||
const saveNoticeId = 'site-edit-save-notice';
|
||||
const { params } = useLocation();
|
||||
const { sendEvent } = useContext( CustomizeStoreContext );
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { __unstableMarkLastChangeAsPersistent } =
|
||||
useDispatch( blockEditorStore );
|
||||
|
||||
const { createSuccessNotice, createErrorNotice, removeNotice } =
|
||||
useDispatch( noticesStore );
|
||||
|
||||
const { dirtyCurrentEntity, countUnsavedChanges, isDirty, isSaving } =
|
||||
useSelect(
|
||||
( select ) => {
|
||||
const {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalGetDirtyEntityRecords,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
isSavingEntityRecord,
|
||||
} = select( coreStore );
|
||||
const dirtyEntityRecords =
|
||||
__experimentalGetDirtyEntityRecords();
|
||||
let calcDirtyCurrentEntity = null;
|
||||
|
||||
if ( dirtyEntityRecords.length === 1 ) {
|
||||
// if we are on global styles
|
||||
if (
|
||||
params.path?.includes( 'color-palette' ) ||
|
||||
params.path?.includes( 'fonts' )
|
||||
) {
|
||||
calcDirtyCurrentEntity = dirtyEntityRecords.find(
|
||||
// @ts-ignore No types for this exist yet.
|
||||
( record ) => record.name === 'globalStyles'
|
||||
);
|
||||
}
|
||||
// if we are on pages
|
||||
else if ( params.postId ) {
|
||||
calcDirtyCurrentEntity = dirtyEntityRecords.find(
|
||||
// @ts-ignore No types for this exist yet.
|
||||
( record ) =>
|
||||
record.name === params.postType &&
|
||||
String( record.key ) === params.postId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dirtyCurrentEntity: calcDirtyCurrentEntity,
|
||||
isDirty: dirtyEntityRecords.length > 0,
|
||||
isSaving: dirtyEntityRecords.some(
|
||||
( record: {
|
||||
kind: string;
|
||||
name: string;
|
||||
key: string;
|
||||
} ) =>
|
||||
isSavingEntityRecord(
|
||||
record.kind,
|
||||
record.name,
|
||||
record.key
|
||||
)
|
||||
),
|
||||
countUnsavedChanges: dirtyEntityRecords.length,
|
||||
};
|
||||
},
|
||||
[ params.path, params.postType, params.postId ]
|
||||
);
|
||||
|
||||
const {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
editEntityRecord,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
saveEditedEntityRecord,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits,
|
||||
} = useDispatch( coreStore );
|
||||
|
||||
const saveCurrentEntity = async () => {
|
||||
if ( ! dirtyCurrentEntity ) return;
|
||||
|
||||
removeNotice( saveNoticeId );
|
||||
const { kind, name, key, property } = dirtyCurrentEntity;
|
||||
|
||||
try {
|
||||
if ( dirtyCurrentEntity.kind === 'root' && name === 'site' ) {
|
||||
await saveSpecifiedEntityEdits( 'root', 'site', undefined, [
|
||||
property,
|
||||
] );
|
||||
} else {
|
||||
if (
|
||||
PUBLISH_ON_SAVE_ENTITIES.some(
|
||||
( typeToPublish ) =>
|
||||
typeToPublish.kind === kind &&
|
||||
typeToPublish.name === name
|
||||
)
|
||||
) {
|
||||
editEntityRecord( kind, name, key, { status: 'publish' } );
|
||||
}
|
||||
|
||||
await saveEditedEntityRecord( kind, name, key );
|
||||
}
|
||||
|
||||
__unstableMarkLastChangeAsPersistent();
|
||||
|
||||
createSuccessNotice( __( 'Site updated.', 'woocommerce' ), {
|
||||
type: 'snackbar',
|
||||
id: saveNoticeId,
|
||||
} );
|
||||
} catch ( error ) {
|
||||
createErrorNotice(
|
||||
`${ __( 'Saving failed.', 'woocommerce' ) } ${ error }`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderButton = () => {
|
||||
// if we have only one unsaved change and it matches current context, we can show a more specific label
|
||||
let label = dirtyCurrentEntity
|
||||
? __( 'Save', 'woocommerce' )
|
||||
: sprintf(
|
||||
// translators: %d: number of unsaved changes (number).
|
||||
_n(
|
||||
'Review %d change…',
|
||||
'Review %d changes…',
|
||||
countUnsavedChanges,
|
||||
'woocommerce'
|
||||
),
|
||||
countUnsavedChanges
|
||||
);
|
||||
|
||||
if ( isSaving ) {
|
||||
label = __( 'Saving', 'woocommerce' );
|
||||
}
|
||||
|
||||
if ( dirtyCurrentEntity ) {
|
||||
return (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={ saveCurrentEntity }
|
||||
isBusy={ isSaving }
|
||||
disabled={ isSaving }
|
||||
aria-disabled={ isSaving }
|
||||
className="edit-site-save-hub__button"
|
||||
// @ts-ignore No types for this exist yet.
|
||||
|
||||
__next40pxDefaultSize
|
||||
>
|
||||
{ label }
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
const disabled = isSaving || ! isDirty;
|
||||
|
||||
if ( ! isSaving && ! isDirty ) {
|
||||
return (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={ () => {
|
||||
sendEvent( 'FINISH_CUSTOMIZATION' );
|
||||
} }
|
||||
className="edit-site-save-hub__button"
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__next40pxDefaultSize
|
||||
>
|
||||
{ __( 'Done', 'woocommerce' ) }
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SaveButton
|
||||
className="edit-site-save-hub__button"
|
||||
variant={ disabled ? null : 'primary' }
|
||||
showTooltip={ false }
|
||||
icon={ disabled && ! isSaving ? check : null }
|
||||
defaultLabel={ label }
|
||||
__next40pxDefaultSize
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack className="edit-site-save-hub" alignment="right" spacing={ 4 }>
|
||||
{ renderButton() }
|
||||
</HStack>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { Link } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
||||
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||
|
||||
export const SidebarNavigationScreenColorPalette = () => {
|
||||
return (
|
||||
<SidebarNavigationScreen
|
||||
title={ __( 'Change the color palette', 'woocommerce' ) }
|
||||
description={ createInterpolateElement(
|
||||
__(
|
||||
'Based on the info you shared, our AI tool recommends using this color palette. Want to change it? You can select or add new colors below, or update them later in <EditorLink>Editor</EditorLink> | <StyleLink>Styles</StyleLink>.',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
EditorLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
StyleLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php?path=%2Fwp_global_styles&canvas=edit` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header"></div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { Link } from '@woocommerce/components';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
||||
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||
|
||||
export const SidebarNavigationScreenFooter = () => {
|
||||
return (
|
||||
<SidebarNavigationScreen
|
||||
title={ __( 'Change your footer', 'woocommerce' ) }
|
||||
description={ createInterpolateElement(
|
||||
__(
|
||||
"Select a new header from the options below. Your header includes your site's navigation and will be added to every page. You can continue customizing this via the <EditorLink>Editor</EditorLink>.",
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
EditorLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header"></div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { Link } from '@woocommerce/components';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
||||
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||
|
||||
export const SidebarNavigationScreenHeader = () => {
|
||||
return (
|
||||
<SidebarNavigationScreen
|
||||
title={ __( 'Change your header', 'woocommerce' ) }
|
||||
description={ createInterpolateElement(
|
||||
__(
|
||||
"Select a new header from the options below. Your header includes your site's navigation and will be added to every page. You can continue customizing this via the <EditorLink>Editor</EditorLink>.",
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
EditorLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header"></div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { Link } from '@woocommerce/components';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
||||
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||
|
||||
export const SidebarNavigationScreenHomepage = () => {
|
||||
return (
|
||||
<SidebarNavigationScreen
|
||||
title={ __( 'Change your homepage', 'woocommerce' ) }
|
||||
description={ createInterpolateElement(
|
||||
__(
|
||||
'Based on the most successful stores in your industry and location, our AI tool has recommended this template for your business. Prefer a different layout? Choose from the templates below now, or later via the <EditorLink>Editor</EditorLink>.',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
EditorLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header"></div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
||||
|
||||
export const SidebarNavigationScreenLogo = () => {
|
||||
return (
|
||||
<SidebarNavigationScreen
|
||||
title={ __( 'Add your logo', 'woocommerce' ) }
|
||||
description={ __(
|
||||
"Ensure your store is on-brand by adding your logo. For best results, upload a SVG or PNG that's a minimum of 300px wide.",
|
||||
'woocommerce'
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header"></div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalItemGroup as ItemGroup,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalNavigatorButton as NavigatorButton,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalHeading as Heading,
|
||||
} from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
siteLogo,
|
||||
color,
|
||||
typography,
|
||||
header,
|
||||
home,
|
||||
footer,
|
||||
pages,
|
||||
} from '@wordpress/icons';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import SidebarNavigationItem from '@wordpress/edit-site/build-module/components/sidebar-navigation-item';
|
||||
import { Link } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
||||
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||
|
||||
export const SidebarNavigationScreenMain = () => {
|
||||
return (
|
||||
<SidebarNavigationScreen
|
||||
isRoot
|
||||
title={ __( "Let's get creative", 'woocommerce' ) }
|
||||
description={ createInterpolateElement(
|
||||
__(
|
||||
'Use our style and layout tools to customize the design of your store. Content and images can be added or changed via the <EditorLink>Editor</EditorLink> later.',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
EditorLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header">
|
||||
<Heading level={ 2 }>
|
||||
{ __( 'Style', 'woocommerce' ) }
|
||||
</Heading>
|
||||
</div>
|
||||
<ItemGroup>
|
||||
<NavigatorButton
|
||||
as={ SidebarNavigationItem }
|
||||
path="/customize-store/logo"
|
||||
withChevron
|
||||
icon={ siteLogo }
|
||||
>
|
||||
{ __( 'Add your logo', 'woocommerce' ) }
|
||||
</NavigatorButton>
|
||||
<NavigatorButton
|
||||
as={ SidebarNavigationItem }
|
||||
path="/customize-store/color-palette"
|
||||
withChevron
|
||||
icon={ color }
|
||||
>
|
||||
{ __( 'Change the color palette', 'woocommerce' ) }
|
||||
</NavigatorButton>
|
||||
<NavigatorButton
|
||||
as={ SidebarNavigationItem }
|
||||
path="/customize-store/typography"
|
||||
withChevron
|
||||
icon={ typography }
|
||||
>
|
||||
{ __( 'Change fonts', 'woocommerce' ) }
|
||||
</NavigatorButton>
|
||||
</ItemGroup>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header">
|
||||
<Heading level={ 2 }>
|
||||
{ __( 'Layout', 'woocommerce' ) }
|
||||
</Heading>
|
||||
</div>
|
||||
<ItemGroup>
|
||||
<NavigatorButton
|
||||
as={ SidebarNavigationItem }
|
||||
path="/customize-store/header"
|
||||
withChevron
|
||||
icon={ header }
|
||||
>
|
||||
{ __( 'Change your header', 'woocommerce' ) }
|
||||
</NavigatorButton>
|
||||
<NavigatorButton
|
||||
as={ SidebarNavigationItem }
|
||||
path="/customize-store/homepage"
|
||||
withChevron
|
||||
icon={ home }
|
||||
>
|
||||
{ __( 'Change your homepage', 'woocommerce' ) }
|
||||
</NavigatorButton>
|
||||
<NavigatorButton
|
||||
as={ SidebarNavigationItem }
|
||||
path="/customize-store/footer"
|
||||
withChevron
|
||||
icon={ footer }
|
||||
>
|
||||
{ __( 'Change your footer', 'woocommerce' ) }
|
||||
</NavigatorButton>
|
||||
<NavigatorButton
|
||||
as={ SidebarNavigationItem }
|
||||
path="/customize-store/pages"
|
||||
withChevron
|
||||
icon={ pages }
|
||||
>
|
||||
{ __( 'Add and edit other pages', 'woocommerce' ) }
|
||||
</NavigatorButton>
|
||||
</ItemGroup>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
||||
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||
|
||||
export const SidebarNavigationScreenPages = () => {
|
||||
return (
|
||||
<SidebarNavigationScreen
|
||||
title={ __( 'Add more pages', 'woocommerce' ) }
|
||||
description={ createInterpolateElement(
|
||||
__(
|
||||
"Enhance your customers' experience by customizing existing pages or adding new ones. You can continue customizing and adding pages later in <EditorLink>Editor</EditorLink> | <PageLink>Pages</PageLink>.",
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
EditorLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
PageLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/edit.php?post_type=page` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header"></div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { Link } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
||||
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||
|
||||
export const SidebarNavigationScreenTypography = () => {
|
||||
return (
|
||||
<SidebarNavigationScreen
|
||||
title={ __( 'Change your font', 'woocommerce' ) }
|
||||
description={ createInterpolateElement(
|
||||
__(
|
||||
"AI has selected a font pairing that's the best fit for your business. If you'd like to change them, select a new option below now, or later in <EditorLink>Editor</EditorLink> | <StyleLink>Styles</StyleLink>.",
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
EditorLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
StyleLink: (
|
||||
<Link
|
||||
href={ `${ ADMIN_URL }/site-editor.php?path=%2Fwp_global_styles&canvas=edit` }
|
||||
type="external"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
content={
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen-patterns__group-header"></div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,141 @@
|
|||
// Reference: https://github.com/WordPress/gutenberg/blob/v16.4.0/packages/edit-site/src/components/sidebar-navigation-screen/index.js
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { useContext } from '@wordpress/element';
|
||||
import {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalHStack as HStack,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalHeading as Heading,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalUseNavigator as useNavigator,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalVStack as VStack,
|
||||
} from '@wordpress/components';
|
||||
import { isRTL, __ } from '@wordpress/i18n';
|
||||
import { chevronRight, chevronLeft } from '@wordpress/icons';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import SidebarButton from '@wordpress/edit-site/build-module/components/sidebar-button';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomizeStoreContext } from '../';
|
||||
const { useLocation } = unlock( routerPrivateApis );
|
||||
|
||||
export const SidebarNavigationScreen = ( {
|
||||
isRoot,
|
||||
title,
|
||||
actions,
|
||||
meta,
|
||||
content,
|
||||
footer,
|
||||
description,
|
||||
backPath: backPathProp,
|
||||
}: {
|
||||
isRoot?: boolean;
|
||||
title: string;
|
||||
actions?: React.ReactNode;
|
||||
meta?: React.ReactNode;
|
||||
content: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
backPath?: string;
|
||||
} ) => {
|
||||
const { sendEvent } = useContext( CustomizeStoreContext );
|
||||
const location = useLocation();
|
||||
const navigator = useNavigator();
|
||||
const icon = isRTL() ? chevronRight : chevronLeft;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack
|
||||
className={ classnames(
|
||||
'edit-site-sidebar-navigation-screen__main',
|
||||
{
|
||||
'has-footer': !! footer,
|
||||
}
|
||||
) }
|
||||
spacing={ 0 }
|
||||
justify="flex-start"
|
||||
>
|
||||
<HStack
|
||||
spacing={ 4 }
|
||||
alignment="flex-start"
|
||||
className="edit-site-sidebar-navigation-screen__title-icon"
|
||||
>
|
||||
{ ! isRoot && (
|
||||
<SidebarButton
|
||||
onClick={ () => {
|
||||
const backPath =
|
||||
backPathProp ?? location.state?.backPath;
|
||||
if ( backPath ) {
|
||||
navigator.goTo( backPath, {
|
||||
isBack: true,
|
||||
} );
|
||||
} else {
|
||||
navigator.goToParent();
|
||||
}
|
||||
} }
|
||||
icon={ icon }
|
||||
label={ __( 'Back', 'woocommerce' ) }
|
||||
showTooltip={ false }
|
||||
/>
|
||||
) }
|
||||
{ isRoot && (
|
||||
<SidebarButton
|
||||
onClick={ () => {
|
||||
sendEvent( 'GO_BACK_TO_DESIGN_WITH_AI' );
|
||||
} }
|
||||
icon={ icon }
|
||||
label={ __( 'Back', 'woocommerce' ) }
|
||||
showTooltip={ false }
|
||||
/>
|
||||
) }
|
||||
<Heading
|
||||
className="edit-site-sidebar-navigation-screen__title"
|
||||
color={ '#e0e0e0' /* $gray-200 */ }
|
||||
level={ 1 }
|
||||
size={ 20 }
|
||||
>
|
||||
{ title }
|
||||
</Heading>
|
||||
{ actions && (
|
||||
<div className="edit-site-sidebar-navigation-screen__actions">
|
||||
{ actions }
|
||||
</div>
|
||||
) }
|
||||
</HStack>
|
||||
{ meta && (
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen__meta">
|
||||
{ meta }
|
||||
</div>
|
||||
</>
|
||||
) }
|
||||
|
||||
<div className="edit-site-sidebar-navigation-screen__content">
|
||||
{ description && (
|
||||
<p className="edit-site-sidebar-navigation-screen__description">
|
||||
{ description }
|
||||
</p>
|
||||
) }
|
||||
{ content }
|
||||
</div>
|
||||
</VStack>
|
||||
{ footer && (
|
||||
<footer className="edit-site-sidebar-navigation-screen__footer">
|
||||
{ footer }
|
||||
</footer>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,127 @@
|
|||
// Reference: https://github.com/WordPress/gutenberg/blob/v16.4.0/packages/edit-site/src/components/site-hub/index.js
|
||||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__unstableMotion as motion,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__unstableAnimatePresence as AnimatePresence,
|
||||
// @ts-ignore No types for this exist yet.
|
||||
__experimentalHStack as HStack,
|
||||
} from '@wordpress/components';
|
||||
import { useReducedMotion } from '@wordpress/compose';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { forwardRef } from '@wordpress/element';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import SiteIcon from '@wordpress/edit-site/build-module/components/site-icon';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
const HUB_ANIMATION_DURATION = 0.3;
|
||||
|
||||
export const SiteHub = forwardRef(
|
||||
(
|
||||
{
|
||||
isTransparent,
|
||||
...restProps
|
||||
}: {
|
||||
isTransparent: boolean;
|
||||
className: string;
|
||||
as: string;
|
||||
variants: motion.Variants;
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { siteTitle } = useSelect( ( select ) => {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { getSite } = select( coreStore );
|
||||
|
||||
return {
|
||||
siteTitle: getSite()?.title,
|
||||
};
|
||||
}, [] );
|
||||
|
||||
const disableMotion = useReducedMotion();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={ ref }
|
||||
{ ...restProps }
|
||||
className={ classnames(
|
||||
'edit-site-site-hub',
|
||||
restProps.className
|
||||
) }
|
||||
initial={ false }
|
||||
transition={ {
|
||||
type: 'tween',
|
||||
duration: disableMotion ? 0 : HUB_ANIMATION_DURATION,
|
||||
ease: 'easeOut',
|
||||
} }
|
||||
>
|
||||
<HStack
|
||||
justify="space-between"
|
||||
alignment="center"
|
||||
className="edit-site-site-hub__container"
|
||||
>
|
||||
<HStack
|
||||
justify="flex-start"
|
||||
className="edit-site-site-hub__text-content"
|
||||
spacing="0"
|
||||
>
|
||||
<motion.div
|
||||
className={ classnames(
|
||||
'edit-site-site-hub__view-mode-toggle-container',
|
||||
{
|
||||
'has-transparent-background': isTransparent,
|
||||
}
|
||||
) }
|
||||
layout
|
||||
transition={ {
|
||||
type: 'tween',
|
||||
duration: disableMotion
|
||||
? 0
|
||||
: HUB_ANIMATION_DURATION,
|
||||
ease: 'easeOut',
|
||||
} }
|
||||
>
|
||||
<SiteIcon className="edit-site-layout__view-mode-toggle-icon" />
|
||||
</motion.div>
|
||||
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
layout={ false }
|
||||
animate={ {
|
||||
opacity: 1,
|
||||
} }
|
||||
exit={ {
|
||||
opacity: 0,
|
||||
} }
|
||||
className={ classnames(
|
||||
'edit-site-site-hub__site-title',
|
||||
{ 'is-transparent': isTransparent }
|
||||
) }
|
||||
transition={ {
|
||||
type: 'tween',
|
||||
duration: disableMotion ? 0 : 0.2,
|
||||
ease: 'easeOut',
|
||||
delay: 0.1,
|
||||
} }
|
||||
>
|
||||
{ decodeEntities( siteTitle ) }
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -0,0 +1,220 @@
|
|||
|
||||
@mixin custom-scrollbars-on-hover($handle-color, $handle-color-hover) {
|
||||
|
||||
// WebKit
|
||||
&::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: $handle-color;
|
||||
border-radius: 8px;
|
||||
border: 3px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
&:hover::-webkit-scrollbar-thumb, // This needs specificity.
|
||||
&:focus::-webkit-scrollbar-thumb,
|
||||
&:focus-within::-webkit-scrollbar-thumb {
|
||||
background-color: $handle-color-hover;
|
||||
}
|
||||
|
||||
// Firefox 109+ and Chrome 111+
|
||||
scrollbar-width: thin;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
scrollbar-color: $handle-color transparent; // Syntax, "dark", "light", or "#handle-color #track-color"
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
scrollbar-color: $handle-color-hover transparent;
|
||||
}
|
||||
|
||||
// Needed to fix a Safari rendering issue.
|
||||
will-change: transform;
|
||||
|
||||
// Always show scrollbar on Mobile devices.
|
||||
@media (hover: none) {
|
||||
& {
|
||||
scrollbar-color: $handle-color-hover transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-profile-wizard__step-assemblerHub {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.edit-site-layout {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
min-height: 100vh;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
|
||||
/* Sidebar Header */
|
||||
.edit-site-layout__hub {
|
||||
width: 380px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.edit-site-site-hub__view-mode-toggle-container {
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.edit-site-sidebar-navigation-screen__title-icon {
|
||||
align-items: center;
|
||||
padding-top: 80px;
|
||||
padding-bottom: 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.edit-site-sidebar-navigation-screen__title-icon,
|
||||
.edit-site-site-hub__view-mode-toggle-container,
|
||||
.edit-site-layout__view-mode-toggle-icon.edit-site-site-icon {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
|
||||
.edit-site-site-hub__site-title {
|
||||
color: $gray-900;
|
||||
font-size: 0.8125rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 153.846% */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.edit-site-site-icon__image {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.edit-site-site-hub__view-mode-toggle-container {
|
||||
padding: 16px 12px 0 16px;
|
||||
|
||||
.edit-site-layout__view-mode-toggle,
|
||||
.edit-site-layout__view-mode-toggle-icon.edit-site-site-icon,
|
||||
.edit-site-site-icon__icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.edit-site-layout__sidebar-region {
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
.edit-site-layout__sidebar {
|
||||
.edit-site-sidebar__content > div {
|
||||
padding: 0 16px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.edit-site-sidebar-button {
|
||||
color: $gray-900;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.edit-site-sidebar-navigation-screen__title {
|
||||
font-size: 1rem;
|
||||
color: $gray-900;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 24px; /* 150% */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.edit-site-sidebar-navigation-screen__description {
|
||||
color: $gray-700;
|
||||
font-size: 0.8125rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 153.846% */
|
||||
}
|
||||
|
||||
.edit-site-sidebar-navigation-screen__content .components-heading {
|
||||
color: $gray-700;
|
||||
font-size: 0.6875rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 16px; /* 145.455% */
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.edit-site-sidebar-navigation-item {
|
||||
border-radius: 4px;
|
||||
padding: 8px 8px 8px 16px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
width: 348px;
|
||||
|
||||
&:hover {
|
||||
background: #ededed;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #171717;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
color: #171717;
|
||||
background: #fcfcfc;
|
||||
border: 1.5px solid var(--wp-admin-theme-color);
|
||||
}
|
||||
|
||||
.components-flex-item {
|
||||
color: $gray-900;
|
||||
font-size: 0.8125rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px; /* 123.077% */
|
||||
letter-spacing: -0.078px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-site-sidebar-navigation-item.components-item .edit-site-sidebar-navigation-item__drilldown-indicator {
|
||||
fill: #ccc;
|
||||
}
|
||||
|
||||
.edit-site-save-hub {
|
||||
border-top: 0;
|
||||
padding: 32px 29px 32px 35px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Preview Canvas */
|
||||
.edit-site-layout__canvas {
|
||||
bottom: 16px;
|
||||
top: 16px;
|
||||
width: calc(100% - 16px);
|
||||
}
|
||||
|
||||
.edit-site-layout__canvas .components-resizable-box__container {
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.25), 0 6px 10px 0 rgba(0, 0, 0, 0.02), 0 13px 15px 0 rgba(0, 0, 0, 0.03), 0 15px 20px 0 rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.woocommerce-customize-store__block-editor,
|
||||
.edit-site-layout:not(.is-full-canvas) .edit-site-layout__canvas > div .interface-interface-skeleton__content {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.interface-interface-skeleton__content {
|
||||
@include custom-scrollbars-on-hover(transparent, $gray-600);
|
||||
}
|
||||
|
||||
.edit-site-resizable-frame__inner-content {
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ export const DesignWithAi: CustomizeStoreComponent = ( { sendEvent } ) => {
|
|||
<>
|
||||
<h1>Design with AI</h1>
|
||||
<button onClick={ () => sendEvent( { type: 'THEME_SUGGESTED' } ) }>
|
||||
Back to intro
|
||||
Assembler Hub
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
declare global {
|
||||
interface Window {
|
||||
wcBlockSettings: {
|
||||
[ key: string ]: unknown;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*~ If your module exports nothing, you'll need this line. Otherwise, delete it */
|
||||
export {};
|
|
@ -16,7 +16,7 @@ import {
|
|||
actions as introActions,
|
||||
} from './intro';
|
||||
import { DesignWithAi, events as designWithAiEvents } from './design-with-ai';
|
||||
import { events as assemblerHubEvents } from './assembler-hub';
|
||||
import { AssemblerHub, events as assemblerHubEvents } from './assembler-hub';
|
||||
import { findComponentMeta } from '~/utils/xstate/find-component';
|
||||
import {
|
||||
CustomizeStoreComponentMeta,
|
||||
|
@ -86,10 +86,10 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
target: 'backToHomescreen',
|
||||
},
|
||||
SELECTED_NEW_THEME: {
|
||||
target: '? Appearance Task ?',
|
||||
target: 'appearanceTask',
|
||||
},
|
||||
SELECTED_BROWSE_ALL_THEMES: {
|
||||
target: '? Appearance Task ?',
|
||||
target: 'appearanceTask',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -114,14 +114,20 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
},
|
||||
},
|
||||
assemblerHub: {
|
||||
meta: {
|
||||
component: AssemblerHub,
|
||||
},
|
||||
on: {
|
||||
FINISH_CUSTOMIZATION: {
|
||||
target: 'backToHomescreen',
|
||||
},
|
||||
GO_BACK_TO_DESIGN_WITH_AI: {
|
||||
target: 'designWithAi',
|
||||
},
|
||||
},
|
||||
},
|
||||
backToHomescreen: {},
|
||||
'? Appearance Task ?': {},
|
||||
appearanceTask: {},
|
||||
},
|
||||
} );
|
||||
|
||||
|
|
|
@ -37,6 +37,11 @@ export const Intro: CustomizeStoreComponent = ( { sendEvent, context } ) => {
|
|||
<button onClick={ () => sendEvent( { type: 'DESIGN_WITH_AI' } ) }>
|
||||
Design with AI
|
||||
</button>
|
||||
<button
|
||||
onClick={ () => sendEvent( { type: 'SELECTED_ACTIVE_THEME' } ) }
|
||||
>
|
||||
Assembler Hub
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -319,7 +319,7 @@ export const getPages = () => {
|
|||
if ( window.wcAdminFeatures[ 'customize-store' ] ) {
|
||||
pages.push( {
|
||||
container: CustomizeStore,
|
||||
path: '/customize-store',
|
||||
path: '/customize-store/*',
|
||||
breadcrumbs: [
|
||||
...initialBreadcrumbs,
|
||||
__( 'Customize Your Store', 'woocommerce' ),
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerPlugin } from '@wordpress/plugins';
|
||||
import { WooOnboardingTaskListItem } from '@woocommerce/onboarding';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
|
||||
const CustomizeStoreTaskItem = () => (
|
||||
<WooOnboardingTaskListItem id="customize-store">
|
||||
{ ( {
|
||||
defaultTaskItem: DefaultTaskItem,
|
||||
}: {
|
||||
defaultTaskItem: ( props: { onClick: () => void } ) => JSX.Element;
|
||||
} ) => (
|
||||
<DefaultTaskItem
|
||||
onClick={ () => {
|
||||
// We need to use window.location.href instead of navigateTo because we need to initiate a full page refresh to ensure that all dependencies are loaded.
|
||||
window.location.href = getAdminLink(
|
||||
'admin.php?page=wc-admin&path=%2Fcustomize-store'
|
||||
);
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</WooOnboardingTaskListItem>
|
||||
);
|
||||
|
||||
registerPlugin( 'woocommerce-admin-task-customize-store', {
|
||||
// @ts-expect-error scope is not defined in the type definition but it is a valid property
|
||||
scope: 'woocommerce-tasks',
|
||||
render: CustomizeStoreTaskItem,
|
||||
} );
|
|
@ -10,6 +10,7 @@ import './tax';
|
|||
import './woocommerce-payments';
|
||||
import './purchase';
|
||||
import './deprecated-tasks';
|
||||
import './customize-store-tasklist-item';
|
||||
|
||||
const possiblyImportProductTask = async () => {
|
||||
if ( isImportProduct() ) {
|
||||
|
|
|
@ -66,16 +66,19 @@
|
|||
"@wordpress/date": "wp-6.0",
|
||||
"@wordpress/dom": "wp-6.0",
|
||||
"@wordpress/dom-ready": "wp-6.0",
|
||||
"@wordpress/edit-site": "5.15.0",
|
||||
"@wordpress/element": "wp-6.0",
|
||||
"@wordpress/hooks": "wp-6.0",
|
||||
"@wordpress/html-entities": "wp-6.0",
|
||||
"@wordpress/i18n": "wp-6.0",
|
||||
"@wordpress/icons": "wp-6.0",
|
||||
"@wordpress/interface": "^5.15.0",
|
||||
"@wordpress/keycodes": "wp-6.0",
|
||||
"@wordpress/media-utils": "wp-6.0",
|
||||
"@wordpress/notices": "wp-6.0",
|
||||
"@wordpress/plugins": "wp-6.0",
|
||||
"@wordpress/primitives": "wp-6.0",
|
||||
"@wordpress/router": "0.7.0",
|
||||
"@wordpress/url": "wp-6.0",
|
||||
"@wordpress/viewport": "wp-6.0",
|
||||
"@wordpress/warning": "wp-6.0",
|
||||
|
|
|
@ -216,6 +216,12 @@ const webpackConfig = {
|
|||
// The external wp.components does not include ui components, so we need to skip requesting to external here.
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( request.startsWith( '@wordpress/edit-site' ) ) {
|
||||
// The external wp.editSite does not include edit-site components, so we need to skip requesting to external here. We can remove this once the edit-site components are exported in the external wp.editSite.
|
||||
// We use the edit-site components in the customize store.
|
||||
return null;
|
||||
}
|
||||
},
|
||||
} ),
|
||||
// Reduces data for moment-timezone.
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store assembler hub
|
|
@ -8,6 +8,16 @@ use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
|||
* Customize Your Store Task
|
||||
*/
|
||||
class CustomizeStore extends Task {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list ) {
|
||||
parent::__construct( $task_list );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_site_editor_scripts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
|
@ -63,11 +73,114 @@ class CustomizeStore extends Task {
|
|||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
* Possibly add site editor scripts.
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'wp-admin/admin.php?page=wc-admin&path=%2Fcustomize-store' );
|
||||
public function possibly_add_site_editor_scripts() {
|
||||
$is_customize_store_pages = (
|
||||
isset( $_GET['page'] ) &&
|
||||
'wc-admin' === $_GET['page'] &&
|
||||
isset( $_GET['path'] ) &&
|
||||
str_starts_with( wc_clean( wp_unslash( $_GET['path'] ) ), '/customize-store' )
|
||||
);
|
||||
if ( ! $is_customize_store_pages ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See: https://github.com/WordPress/WordPress/blob/master/wp-admin/site-editor.php.
|
||||
if ( ! wp_is_block_theme() ) {
|
||||
wp_die( esc_html__( 'The theme you are currently using is not compatible.', 'woocommerce' ) );
|
||||
}
|
||||
global $editor_styles;
|
||||
|
||||
// Flag that we're loading the block editor.
|
||||
$current_screen = get_current_screen();
|
||||
$current_screen->is_block_editor( true );
|
||||
|
||||
// Default to is-fullscreen-mode to avoid jumps in the UI.
|
||||
add_filter(
|
||||
'admin_body_class',
|
||||
static function( $classes ) {
|
||||
return "$classes is-fullscreen-mode";
|
||||
}
|
||||
);
|
||||
|
||||
$block_editor_context = new \WP_Block_Editor_Context( array( 'name' => 'core/edit-site' ) );
|
||||
$indexed_template_types = array();
|
||||
foreach ( get_default_block_template_types() as $slug => $template_type ) {
|
||||
$template_type['slug'] = (string) $slug;
|
||||
$indexed_template_types[] = $template_type;
|
||||
}
|
||||
|
||||
$custom_settings = array(
|
||||
'siteUrl' => site_url(),
|
||||
'postsPerPage' => get_option( 'posts_per_page' ),
|
||||
'styles' => get_block_editor_theme_styles(),
|
||||
'defaultTemplateTypes' => $indexed_template_types,
|
||||
'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(),
|
||||
'supportsLayout' => wp_theme_has_theme_json(),
|
||||
'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ),
|
||||
);
|
||||
|
||||
// Add additional back-compat patterns registered by `current_screen` et al.
|
||||
$custom_settings['__experimentalAdditionalBlockPatterns'] = \WP_Block_Patterns_Registry::get_instance()->get_all_registered( true );
|
||||
$custom_settings['__experimentalAdditionalBlockPatternCategories'] = \WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered( true );
|
||||
|
||||
$editor_settings = get_block_editor_settings( $custom_settings, $block_editor_context );
|
||||
$active_global_styles_id = \WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
|
||||
$active_theme = get_stylesheet();
|
||||
$preload_paths = array(
|
||||
array( '/wp/v2/media', 'OPTIONS' ),
|
||||
'/wp/v2/types?context=view',
|
||||
'/wp/v2/types/wp_template?context=edit',
|
||||
'/wp/v2/types/wp_template-part?context=edit',
|
||||
'/wp/v2/templates?context=edit&per_page=-1',
|
||||
'/wp/v2/template-parts?context=edit&per_page=-1',
|
||||
'/wp/v2/themes?context=edit&status=active',
|
||||
'/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit',
|
||||
'/wp/v2/global-styles/' . $active_global_styles_id,
|
||||
'/wp/v2/global-styles/themes/' . $active_theme,
|
||||
);
|
||||
|
||||
block_editor_rest_api_preload( $preload_paths, $block_editor_context );
|
||||
|
||||
wp_add_inline_script(
|
||||
'wp-blocks',
|
||||
sprintf(
|
||||
'window.wcBlockSettings = %s;',
|
||||
wp_json_encode( $editor_settings )
|
||||
)
|
||||
);
|
||||
|
||||
// Preload server-registered block schemas.
|
||||
wp_add_inline_script(
|
||||
'wp-blocks',
|
||||
'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');'
|
||||
);
|
||||
|
||||
wp_add_inline_script(
|
||||
'wp-blocks',
|
||||
sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( isset( $editor_settings['blockCategories'] ) ? $editor_settings['blockCategories'] : array() ) ),
|
||||
'after'
|
||||
);
|
||||
|
||||
wp_enqueue_script( 'wp-editor' );
|
||||
wp_enqueue_script( 'wp-format-library' ); // Not sure if this is needed.
|
||||
wp_enqueue_script( 'wp-router' );
|
||||
wp_enqueue_style( 'wp-editor' );
|
||||
wp_enqueue_style( 'wp-edit-site' );
|
||||
wp_enqueue_style( 'wp-format-library' );
|
||||
wp_enqueue_media();
|
||||
|
||||
if (
|
||||
current_theme_supports( 'wp-block-styles' ) &&
|
||||
( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 )
|
||||
) {
|
||||
wp_enqueue_style( 'wp-block-library-theme' );
|
||||
}
|
||||
/** This action is documented in wp-admin/edit-form-blocks.php
|
||||
*
|
||||
* @since 8.0.3
|
||||
*/
|
||||
do_action( 'enqueue_block_editor_assets' );
|
||||
}
|
||||
}
|
||||
|
|
2523
pnpm-lock.yaml
2523
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue