[Product Block Editor]: move Modal editor out of the description block edit component (#41878)

This commit is contained in:
Damián Suárez 2023-12-06 18:28:36 -03:00 committed by GitHub
commit 6fab5dc8eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 168 additions and 41 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
[Product Block Editor]: move Modal edittor out of the description block edit component

View File

@ -2,7 +2,7 @@
* External dependencies * External dependencies
*/ */
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { createElement } from '@wordpress/element'; import { createElement, useEffect } from '@wordpress/element';
import { import {
BlockAttributes, BlockAttributes,
BlockInstance, BlockInstance,
@ -19,10 +19,9 @@ import { useEntityProp } from '@wordpress/core-data';
* Internal dependencies * Internal dependencies
*/ */
import { ContentPreview } from '../../../components/content-preview'; import { ContentPreview } from '../../../components/content-preview';
import { ModalEditor } from '../../../components/modal-editor';
import { ProductEditorBlockEditProps } from '../../../types'; import { ProductEditorBlockEditProps } from '../../../types';
import ModalEditorWelcomeGuide from '../../../components/modal-editor-welcome-guide'; import ModalEditorWelcomeGuide from '../../../components/modal-editor-welcome-guide';
import { store as productEditorUiStore } from '../../../store/product-editor-ui'; import { store } from '../../../store/product-editor-ui';
/** /**
* Internal dependencies * Internal dependencies
@ -58,19 +57,43 @@ export function DescriptionBlockEdit( {
'description' 'description'
); );
// Check if the Modal editor is open from the store. // Pick Modal editor data from the store.
const isModalEditorOpen = useSelect( ( select ) => { const { isModalEditorOpen, modalEditorBlocks, hasChanged } = useSelect(
return select( productEditorUiStore ).isModalEditorOpen(); ( select ) => {
}, [] ); return {
isModalEditorOpen: select( store ).isModalEditorOpen(),
modalEditorBlocks: select( store ).getModalEditorBlocks(),
hasChanged: select( store ).getModalEditorContentHasChanged(),
};
},
[]
);
const { openModalEditor, closeModalEditor } = const { openModalEditor, setModalEditorBlocks } = useDispatch( store );
useDispatch( productEditorUiStore );
// Update the description when the blocks change.
useEffect( () => {
if ( ! hasChanged ) {
return;
}
if ( ! modalEditorBlocks?.length ) {
setDescription( '' );
}
const html = serialize( clearDescriptionIfEmpty( modalEditorBlocks ) );
setDescription( html );
}, [ modalEditorBlocks, setDescription, hasChanged ] );
return ( return (
<div { ...blockProps }> <div { ...blockProps }>
<Button <Button
variant="secondary" variant="secondary"
onClick={ () => { onClick={ () => {
if ( description ) {
setModalEditorBlocks( parse( description ) );
}
openModalEditor(); openModalEditor();
recordEvent( 'product_add_description_click' ); recordEvent( 'product_add_description_click' );
} } } }
@ -80,20 +103,6 @@ export function DescriptionBlockEdit( {
: __( 'Add description', 'woocommerce' ) } : __( 'Add description', 'woocommerce' ) }
</Button> </Button>
{ isModalEditorOpen && (
<ModalEditor
initialBlocks={ parse( description ) }
onChange={ ( blocks ) => {
const html = serialize(
clearDescriptionIfEmpty( blocks )
);
setDescription( html );
} }
onClose={ closeModalEditor }
title={ __( 'Edit description', 'woocommerce' ) }
/>
) }
{ !! description.length && ( { !! description.length && (
<ContentPreview content={ description } /> <ContentPreview content={ description } />
) } ) }

View File

@ -6,6 +6,7 @@ import { createElement, useMemo, useLayoutEffect } from '@wordpress/element';
import { useDispatch, useSelect, select as WPSelect } from '@wordpress/data'; import { useDispatch, useSelect, select as WPSelect } from '@wordpress/data';
import { uploadMedia } from '@wordpress/media-utils'; import { uploadMedia } from '@wordpress/media-utils';
import { PluginArea } from '@wordpress/plugins'; import { PluginArea } from '@wordpress/plugins';
import { __ } from '@wordpress/i18n';
import { import {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet. // @ts-ignore No types for this exist yet.
@ -34,6 +35,8 @@ import {
import { useConfirmUnsavedProductChanges } from '../../hooks/use-confirm-unsaved-product-changes'; import { useConfirmUnsavedProductChanges } from '../../hooks/use-confirm-unsaved-product-changes';
import { ProductEditorContext } from '../../types'; import { ProductEditorContext } from '../../types';
import { PostTypeContext } from '../../contexts/post-type-context'; import { PostTypeContext } from '../../contexts/post-type-context';
import { ModalEditor } from '../modal-editor';
import { store as productEditorUiStore } from '../../store/product-editor-ui';
type BlockEditorSettings = Partial< type BlockEditorSettings = Partial<
EditorSettings & EditorBlockListSettings EditorSettings & EditorBlockListSettings
@ -111,10 +114,26 @@ export function BlockEditor( {
updateEditorSettings( settings ?? {} ); updateEditorSettings( settings ?? {} );
}, [ productType, productId ] ); }, [ productType, productId ] );
// Check if the Modal editor is open from the store.
const isModalEditorOpen = useSelect( ( select ) => {
return select( productEditorUiStore ).isModalEditorOpen();
}, [] );
const { closeModalEditor } = useDispatch( productEditorUiStore );
if ( ! blocks ) { if ( ! blocks ) {
return null; return null;
} }
if ( isModalEditorOpen ) {
return (
<ModalEditor
onClose={ closeModalEditor }
title={ __( 'Edit description', 'woocommerce' ) }
/>
);
}
return ( return (
<div className="woocommerce-product-block-editor"> <div className="woocommerce-product-block-editor">
<BlockContextProvider value={ context }> <BlockContextProvider value={ context }>
@ -123,6 +142,7 @@ export function BlockEditor( {
onInput={ onInput } onInput={ onInput }
onChange={ onChange } onChange={ onChange }
settings={ settings } settings={ settings }
useSubRegistry={ false }
> >
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ } { /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore No types for this exist yet. */ } { /* @ts-ignore No types for this exist yet. */ }

View File

@ -31,31 +31,41 @@ import { HeaderToolbar } from './header-toolbar/header-toolbar';
import { ResizableEditor } from './resizable-editor'; import { ResizableEditor } from './resizable-editor';
import { SecondarySidebar } from './secondary-sidebar/secondary-sidebar'; import { SecondarySidebar } from './secondary-sidebar/secondary-sidebar';
import { useEditorHistory } from './hooks/use-editor-history'; import { useEditorHistory } from './hooks/use-editor-history';
import { store as productEditorUiStore } from '../../store/product-editor-ui';
type IframeEditorProps = { type IframeEditorProps = {
closeModal?: () => void;
initialBlocks?: BlockInstance[]; initialBlocks?: BlockInstance[];
onChange?: ( blocks: BlockInstance[] ) => void; onChange?: ( blocks: BlockInstance[] ) => void;
onClose?: () => void; onClose?: () => void;
onInput?: ( blocks: BlockInstance[] ) => void; onInput?: ( blocks: BlockInstance[] ) => void;
settings?: Partial< EditorSettings & EditorBlockListSettings > | undefined; settings?: Partial< EditorSettings & EditorBlockListSettings > | undefined;
showBackButton?: boolean;
}; };
export function IframeEditor( { export function IframeEditor( {
closeModal = () => {},
initialBlocks = [], initialBlocks = [],
onChange = () => {}, onChange = () => {},
onClose, onClose,
onInput = () => {}, onInput = () => {},
settings: __settings, settings: __settings,
showBackButton = false,
}: IframeEditorProps ) { }: IframeEditorProps ) {
const [ resizeObserver ] = useResizeObserver(); const [ resizeObserver ] = useResizeObserver();
const [ blocks, setBlocks ] = useState< BlockInstance[] >( initialBlocks );
const [ temporalBlocks, setTemporalBlocks ] = const [ temporalBlocks, setTemporalBlocks ] =
useState< BlockInstance[] >( initialBlocks ); useState< BlockInstance[] >( initialBlocks );
// Pick the blocks from the store.
const blocks: BlockInstance[] = useSelect( ( select ) => {
return select( productEditorUiStore ).getModalEditorBlocks();
}, [] );
const { setModalEditorBlocks: setBlocks, setModalEditorContentHasChanged } =
useDispatch( productEditorUiStore );
const { appendEdit } = useEditorHistory( { const { appendEdit } = useEditorHistory( {
setBlocks, setBlocks,
} ); } );
const { const {
appendEdit: tempAppendEdit, appendEdit: tempAppendEdit,
hasRedo, hasRedo,
@ -127,15 +137,16 @@ export function IframeEditor( {
onSave={ () => { onSave={ () => {
appendEdit( temporalBlocks ); appendEdit( temporalBlocks );
setBlocks( temporalBlocks ); setBlocks( temporalBlocks );
setModalEditorContentHasChanged( true );
onChange( temporalBlocks ); onChange( temporalBlocks );
closeModal(); onClose?.();
} } } }
onCancel={ () => { onCancel={ () => {
appendEdit( blocks ); appendEdit( blocks );
setBlocks( blocks ); setBlocks( blocks );
onChange( blocks ); onChange( blocks );
setTemporalBlocks( blocks ); setTemporalBlocks( blocks );
closeModal(); onClose?.();
} } } }
/> />
<div className="woocommerce-iframe-editor__main"> <div className="woocommerce-iframe-editor__main">
@ -157,7 +168,7 @@ export function IframeEditor( {
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ } { /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore */ } { /* @ts-ignore */ }
<BlockEditorKeyboardShortcuts.Register /> <BlockEditorKeyboardShortcuts.Register />
{ onClose && ( { showBackButton && onClose && (
<BackButton <BackButton
onClick={ () => { onClick={ () => {
setTimeout( onClose, 550 ); setTimeout( onClose, 550 );

View File

@ -3,6 +3,7 @@
*/ */
import { BlockInstance } from '@wordpress/blocks'; import { BlockInstance } from '@wordpress/blocks';
import { createElement } from '@wordpress/element'; import { createElement } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { import {
EditorSettings, EditorSettings,
EditorBlockListSettings, EditorBlockListSettings,
@ -14,11 +15,12 @@ import { useDebounce } from '@wordpress/compose';
* Internal dependencies * Internal dependencies
*/ */
import { IframeEditor } from '../iframe-editor'; import { IframeEditor } from '../iframe-editor';
import { store as productEditorUiStore } from '../../store/product-editor-ui';
type ModalEditorProps = { type ModalEditorProps = {
initialBlocks?: BlockInstance[]; initialBlocks?: BlockInstance[];
onChange: ( blocks: BlockInstance[] ) => void; onChange?: ( blocks: BlockInstance[] ) => void;
onClose: () => void; onClose?: () => void;
settings?: Partial< EditorSettings & EditorBlockListSettings > | undefined; settings?: Partial< EditorSettings & EditorBlockListSettings > | undefined;
title: string; title: string;
}; };
@ -29,16 +31,19 @@ export function ModalEditor( {
onClose, onClose,
title, title,
}: ModalEditorProps ) { }: ModalEditorProps ) {
const { closeModalEditor } = useDispatch( productEditorUiStore );
const debouncedOnChange = useDebounce( ( blocks: BlockInstance[] ) => { const debouncedOnChange = useDebounce( ( blocks: BlockInstance[] ) => {
onChange( blocks ); onChange?.( blocks );
}, 250 ); }, 250 );
function handleClose() { function handleClose() {
const blocks = debouncedOnChange.flush(); const blocks = debouncedOnChange.flush();
if ( blocks ) { if ( blocks ) {
onChange( blocks ); onChange?.( blocks );
} }
onClose(); closeModalEditor();
onClose?.();
} }
return ( return (
@ -52,7 +57,7 @@ export function ModalEditor( {
initialBlocks={ initialBlocks } initialBlocks={ initialBlocks }
onInput={ debouncedOnChange } onInput={ debouncedOnChange }
onChange={ debouncedOnChange } onChange={ debouncedOnChange }
closeModal={ handleClose } onClose={ handleClose }
/> />
</Modal> </Modal>
); );

View File

@ -1,18 +1,36 @@
/**
* External dependencies
*/
import { BlockInstance } from '@wordpress/blocks';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { import {
ACTION_MODAL_EDITOR_CLOSE, ACTION_MODAL_EDITOR_CLOSE,
ACTION_MODAL_EDITOR_OPEN, ACTION_MODAL_EDITOR_OPEN,
ACTION_MODAL_EDITOR_SET_BLOCKS,
ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
} from './constants'; } from './constants';
const modalEditorActions = { const modalEditorActions = {
openModalEditor: () => ( { openModalEditor: () => ( {
type: ACTION_MODAL_EDITOR_OPEN, type: ACTION_MODAL_EDITOR_OPEN,
} ), } ),
closeModalEditor: () => ( { closeModalEditor: () => ( {
type: ACTION_MODAL_EDITOR_CLOSE, type: ACTION_MODAL_EDITOR_CLOSE,
} ), } ),
setModalEditorBlocks: ( blocks: BlockInstance ) => ( {
type: ACTION_MODAL_EDITOR_SET_BLOCKS,
blocks,
} ),
setModalEditorContentHasChanged: ( hasChanged: boolean ) => ( {
type: ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
hasChanged,
} ),
}; };
export default { export default {

View File

@ -1,5 +1,8 @@
/** /**
* Full editor actions * Full editor actions
*/ */
export const ACTION_MODAL_EDITOR_OPEN = 'MODAL_EDITOROPEN'; export const ACTION_MODAL_EDITOR_OPEN = 'MODAL_EDITOR_OPEN';
export const ACTION_MODAL_EDITOR_CLOSE = 'MODAL_EDITORCLOSE'; export const ACTION_MODAL_EDITOR_CLOSE = 'MODAL_EDITOR_CLOSE';
export const ACTION_MODAL_EDITOR_SET_BLOCKS = 'MODAL_EDITOR_SET_BLOCKS';
export const ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED =
'MODAL_EDITOR_CONTENT_HAS_CHANGED';

View File

@ -2,8 +2,10 @@
* Internal dependencies * Internal dependencies
*/ */
import { import {
ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
ACTION_MODAL_EDITOR_CLOSE, ACTION_MODAL_EDITOR_CLOSE,
ACTION_MODAL_EDITOR_OPEN, ACTION_MODAL_EDITOR_OPEN,
ACTION_MODAL_EDITOR_SET_BLOCKS,
} from './constants'; } from './constants';
import type { import type {
ProductEditorModalEditorAction, ProductEditorModalEditorAction,
@ -16,6 +18,8 @@ import type {
const INITIAL_STATE: ProductEditorUIStateProps = { const INITIAL_STATE: ProductEditorUIStateProps = {
modalEditor: { modalEditor: {
isOpen: false, isOpen: false,
blocks: [],
hasChanged: false,
}, },
}; };
@ -28,16 +32,37 @@ export default function reducer(
return { return {
...state, ...state,
modalEditor: { modalEditor: {
...state.modalEditor,
isOpen: true, isOpen: true,
}, },
}; };
case ACTION_MODAL_EDITOR_CLOSE: case ACTION_MODAL_EDITOR_CLOSE:
return { return {
...state, ...state,
modalEditor: { modalEditor: {
...state.modalEditor,
isOpen: false, isOpen: false,
}, },
}; };
case ACTION_MODAL_EDITOR_SET_BLOCKS:
return {
...state,
modalEditor: {
...state.modalEditor,
blocks: action.blocks || [],
},
};
case ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED:
return {
...state,
modalEditor: {
...state.modalEditor,
hasChanged: action?.hasChanged || false,
},
};
} }
return state; return state;

View File

@ -1,7 +1,11 @@
/**
* External dependencies
*/
import { BlockInstance } from '@wordpress/blocks';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import type { ProductEditorUIStateProps } from './types'; import type { ProductEditorUIStateProps } from './types';
export default { export default {
@ -10,4 +14,16 @@ export default {
) { ) {
return state.modalEditor.isOpen; return state.modalEditor.isOpen;
}, },
getModalEditorBlocks: function getModalEditorBlocks(
state: ProductEditorUIStateProps
): BlockInstance[] {
return state.modalEditor.blocks;
},
getModalEditorContentHasChanged: function getModalEditorContentHasChanged(
state: ProductEditorUIStateProps
): boolean {
return !! state.modalEditor.hasChanged;
},
}; };

View File

@ -1,17 +1,34 @@
/**
* External dependencies
*/
import { BlockInstance } from '@wordpress/blocks';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { import {
ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
ACTION_MODAL_EDITOR_CLOSE, ACTION_MODAL_EDITOR_CLOSE,
ACTION_MODAL_EDITOR_OPEN, ACTION_MODAL_EDITOR_OPEN,
ACTION_MODAL_EDITOR_SET_BLOCKS,
} from './constants'; } from './constants';
export type ProductEditorUIStateProps = { export type ProductEditorUIStateProps = {
modalEditor: { modalEditor: {
isOpen: boolean; isOpen: boolean;
blocks: BlockInstance[];
hasChanged?: boolean;
}; };
}; };
export type ProductEditorModalEditorAction = { export type ProductEditorModalEditorAction = {
type: typeof ACTION_MODAL_EDITOR_OPEN | typeof ACTION_MODAL_EDITOR_CLOSE; type:
| typeof ACTION_MODAL_EDITOR_OPEN
| typeof ACTION_MODAL_EDITOR_CLOSE
| typeof ACTION_MODAL_EDITOR_SET_BLOCKS
| typeof ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED;
blocks?: BlockInstance[];
hasChanged?: boolean;
}; };

View File

@ -26283,7 +26283,6 @@ packages:
/flow-parser@0.223.3: /flow-parser@0.223.3:
resolution: {integrity: sha512-9KxxDKSB22ovMpSULbOL/QAQGPN6M0YMS3PubQvB0jVc4W7QP6VhasIVic7MzKcJSh0BAVs4J6SZjoH0lDDNlg==} resolution: {integrity: sha512-9KxxDKSB22ovMpSULbOL/QAQGPN6M0YMS3PubQvB0jVc4W7QP6VhasIVic7MzKcJSh0BAVs4J6SZjoH0lDDNlg==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
dev: true
/flush-write-stream@1.1.1: /flush-write-stream@1.1.1:
resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==} resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==}
@ -31875,7 +31874,7 @@ packages:
'@babel/register': 7.22.15(@babel/core@7.23.5) '@babel/register': 7.22.15(@babel/core@7.23.5)
babel-core: 7.0.0-bridge.0(@babel/core@7.23.5) babel-core: 7.0.0-bridge.0(@babel/core@7.23.5)
chalk: 4.1.2 chalk: 4.1.2
flow-parser: 0.206.0 flow-parser: 0.223.3
graceful-fs: 4.2.11 graceful-fs: 4.2.11
micromatch: 4.0.5 micromatch: 4.0.5
neo-async: 2.6.2 neo-async: 2.6.2