Add IframeEditor component to product editor (#37570)

* Add initial modal editor

* Add in iframe editor styles

* Convert components to TS

* Sync dependencies

* Remove unused components and props

* Set min height to avoid jumpiness during resize

* Fix up private APIs dependency version

* Update ModalEditor name to IframeEditor

* Add changelog entry

* Remove IframeEditor from details block

* Update frozen lock file

* Add support for missing gutenberg assets function

* Fix lock file after rebase

* Use default editor settings when none are provided

* Remove currently unused editor styles

* Remove unused private apis package

* Fix php lint errors

* Remove unused import

* Pin keycodes version

* Remove another unused import from testing

* Add WC changelog entry
This commit is contained in:
Joshua T Flowers 2023-04-18 10:52:28 -07:00 committed by GitHub
parent 4a6b5ac0cd
commit ebe879d5dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 684 additions and 1185 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add iframe block editor

View File

@ -56,6 +56,7 @@
"@wordpress/icons": "wp-6.0",
"@wordpress/interface": "wp-6.0",
"@wordpress/keyboard-shortcuts": "wp-6.0",
"@wordpress/keycodes": "wp-6.0",
"@wordpress/media-utils": "wp-6.0",
"@wordpress/plugins": "wp-6.0",
"@wordpress/url": "wp-6.0",
@ -85,6 +86,7 @@
"@types/wordpress__data": "^6.0.2",
"@types/wordpress__date": "^3.3.2",
"@types/wordpress__editor": "^13.0.0",
"@types/wordpress__keycodes": "^2.3.1",
"@types/wordpress__media-utils": "^3.0.0",
"@types/wordpress__plugins": "^3.0.0",
"@woocommerce/eslint-plugin": "workspace:*",

View File

@ -0,0 +1,59 @@
/**
* External dependencies
*/
import { createElement, Fragment } from '@wordpress/element';
import {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
__unstableIframe as Iframe,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
__unstableUseMouseMoveTypingReset as useMouseMoveTypingReset,
} from '@wordpress/block-editor';
type EditorCanvasProps = {
enableResizing: boolean;
children: React.ReactNode;
};
export function EditorCanvas( {
enableResizing,
children,
...props
}: EditorCanvasProps ) {
const mouseMoveTypingRef = useMouseMoveTypingReset();
return (
<Iframe
head={
<>
<style>
{
// Forming a "block formatting context" to prevent margin collapsing.
// @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context
`.is-root-container {
padding: 36px;
display: flow-root;
}
body { position: relative; }`
}
</style>
{ enableResizing && (
<style>
{
// Some themes will have `min-height: 100vh` for the root container,
// which isn't a requirement in auto resize mode.
`.is-root-container { min-height: 0 !important; }`
}
</style>
) }
</>
}
ref={ mouseMoveTypingRef }
name="editor-canvas"
className="edit-site-visual-editor__editor-canvas"
{ ...props }
>
{ children }
</Iframe>
);
}

View File

@ -0,0 +1,19 @@
.woocommerce-iframe-editor {
align-items: center;
background-color: #2f2f2f;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
overflow: hidden;
position: fixed;
top: 0;
left: 0;
padding: 132px 48px 48px 208px;
z-index: 1000;
iframe {
width: 100%;
height: 100%;
}
}

View File

@ -0,0 +1,91 @@
/**
* External dependencies
*/
import { BlockInstance } from '@wordpress/blocks';
import { useDispatch, useSelect } from '@wordpress/data';
import { createElement, useEffect, useState } from '@wordpress/element';
import { useResizeObserver } from '@wordpress/compose';
import {
BlockEditorProvider,
BlockList,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
BlockTools,
BlockEditorKeyboardShortcuts,
EditorSettings,
EditorBlockListSettings,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
store as blockEditorStore,
} from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import { EditorCanvas } from './editor-canvas';
import { ResizableEditor } from './resizable-editor';
type IframeEditorProps = {
settings?: Partial< EditorSettings & EditorBlockListSettings > | undefined;
};
export function IframeEditor( { settings }: IframeEditorProps ) {
const [ resizeObserver, sizes ] = useResizeObserver();
const [ blocks, setBlocks ] = useState< BlockInstance[] >( [] );
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This action exists in the block editor store.
const { clearSelectedBlock, updateSettings } =
useDispatch( blockEditorStore );
const parentEditorSettings = useSelect( ( select ) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return select( blockEditorStore ).getSettings();
}, [] );
useEffect( () => {
// Manually update the settings so that __unstableResolvedAssets gets added to the data store.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
updateSettings( productBlockEditorSettings );
}, [] );
return (
<BlockEditorProvider
settings={ {
...( settings || parentEditorSettings ),
templateLock: false,
} }
value={ blocks }
onChange={ setBlocks }
useSubRegistry={ true }
>
<BlockTools
className={ 'woocommerce-iframe-editor' }
onClick={ (
event: React.MouseEvent< HTMLDivElement, MouseEvent >
) => {
// Clear selected block when clicking on the gray background.
if ( event.target === event.currentTarget ) {
clearSelectedBlock();
}
} }
>
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore */ }
<BlockEditorKeyboardShortcuts.Register />
<ResizableEditor
enableResizing={ true }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This accepts numbers or strings.
height={ sizes.height ?? '100%' }
>
<EditorCanvas enableResizing={ true }>
{ resizeObserver }
<BlockList className="edit-site-block-editor__block-list wp-site-blocks" />
</EditorCanvas>
</ResizableEditor>
</BlockTools>
</BlockEditorProvider>
);
}

View File

@ -0,0 +1 @@
export * from './iframe-editor';

View File

@ -0,0 +1,98 @@
/**
* External dependencies
*/
import {
createElement,
useState,
useRef,
useCallback,
} from '@wordpress/element';
import { ResizableBox } from '@wordpress/components';
/**
* Internal dependencies
*/
import ResizeHandle from './resize-handle';
// Removes the inline styles in the drag handles.
const HANDLE_STYLES_OVERRIDE = {
position: undefined,
userSelect: undefined,
cursor: undefined,
width: undefined,
height: undefined,
top: undefined,
right: undefined,
bottom: undefined,
left: undefined,
};
type ResizableEditorProps = {
enableResizing: boolean;
height: number;
children: React.ReactNode;
};
export function ResizableEditor( {
enableResizing,
height,
children,
}: ResizableEditorProps ) {
const [ width, setWidth ] = useState( '100%' );
const resizableRef = useRef< HTMLDivElement >();
const resizeWidthBy = useCallback( ( deltaPixels ) => {
if ( resizableRef.current ) {
setWidth( resizableRef.current.offsetWidth + deltaPixels );
}
}, [] );
return (
<ResizableBox
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This prop was added to ResizeableBox.
ref={ ( api ) => {
resizableRef.current = api?.resizable;
} }
size={ {
width: enableResizing ? width : '100%',
height: enableResizing && height ? height : '100%',
} }
onResizeStop={ ( event, direction, element ) => {
setWidth( element.style.width );
} }
minWidth={ 300 }
maxWidth="100%"
maxHeight="100%"
minHeight={ height }
enable={ {
right: enableResizing,
left: enableResizing,
} }
showHandle={ enableResizing }
// The editor is centered horizontally, resizing it only
// moves half the distance. Hence double the ratio to correctly
// align the cursor to the resizer handle.
resizeRatio={ 2 }
handleComponent={ {
left: (
<ResizeHandle
direction="left"
resizeWidthBy={ resizeWidthBy }
/>
),
right: (
<ResizeHandle
direction="right"
resizeWidthBy={ resizeWidthBy }
/>
),
} }
handleClasses={ undefined }
handleStyles={ {
left: HANDLE_STYLES_OVERRIDE,
right: HANDLE_STYLES_OVERRIDE,
} }
>
{ children }
</ResizableBox>
);
}

View File

@ -0,0 +1,36 @@
.resizable-editor__drag-handle {
-webkit-appearance: none;
appearance: none;
background: none;
border: 0;
border-radius: 2px;
bottom: 0;
cursor: ew-resize;
margin: auto 0;
outline: none;
padding: 0;
position: absolute;
top: 0;
width: 12px;
height: 100px;
&.is-left {
left: -16px;
}
&.is-right {
right: -16px;
}
&:after {
background: #949494;
border-radius: 2px;
bottom: 24px;
content: "";
left: 4px;
position: absolute;
right: 0;
top: 24px;
width: 4px;
}
}

View File

@ -0,0 +1,55 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { createElement, Fragment } from '@wordpress/element';
import { LEFT, RIGHT } from '@wordpress/keycodes';
import { KeyboardEvent } from 'react';
import { VisuallyHidden } from '@wordpress/components';
const DELTA_DISTANCE = 20; // The distance to resize per keydown in pixels.
type ResizeHandleProps = {
direction: 'left' | 'right';
resizeWidthBy: ( width: number ) => void;
};
export default function ResizeHandle( {
direction,
resizeWidthBy,
}: ResizeHandleProps ) {
function handleKeyDown( event: KeyboardEvent< HTMLButtonElement > ) {
const { keyCode } = event;
if (
( direction === 'left' && keyCode === LEFT ) ||
( direction === 'right' && keyCode === RIGHT )
) {
resizeWidthBy( DELTA_DISTANCE );
} else if (
( direction === 'left' && keyCode === RIGHT ) ||
( direction === 'right' && keyCode === LEFT )
) {
resizeWidthBy( -DELTA_DISTANCE );
}
}
return (
<>
<button
className={ `resizable-editor__drag-handle is-${ direction }` }
aria-label={ __( 'Drag to resize', 'woocommerce' ) }
aria-describedby={ `resizable-editor__resize-help-${ direction }` }
onKeyDown={ handleKeyDown }
/>
<VisuallyHidden
id={ `resizable-editor__resize-help-${ direction }` }
>
{ __(
'Use left and right arrow keys to resize the canvas.',
'woocommerce'
) }
</VisuallyHidden>
</>
);
}

View File

@ -0,0 +1,2 @@
@import './iframe-editor.scss';
@import './resize-handle.scss';

View File

@ -12,6 +12,7 @@
@import 'components/block-editor/style.scss';
@import 'blocks/checkbox/editor.scss';
@import 'components/radio-field/style.scss';
@import 'components/iframe-editor/style.scss';
@import 'components/section/editor.scss';
@import 'components/tab/editor.scss';
@import 'components/tabs/style.scss';

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add unresolved assets for iframe editors to editor settings

View File

@ -55,8 +55,9 @@ class Init {
$editor_settings = array();
if ( ! empty( $post_type_object->template ) ) {
$editor_settings['template'] = $post_type_object->template;
$editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false;
$editor_settings['template'] = $post_type_object->template;
$editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false;
$editor_settings['__unstableResolvedAssets'] = $this->get_resolved_assets();
}
$editor_settings = get_block_editor_settings( $editor_settings, $block_editor_context );
@ -115,4 +116,103 @@ class Init {
return $link;
}
/**
* Get the resolved assets needed for the iframe editor.
*
* @return array Styles and scripts.
*/
private function get_resolved_assets() {
if ( function_exists( 'gutenberg_resolve_assets_override' ) ) {
return gutenberg_resolve_assets_override();
}
global $pagenow;
$script_handles = array(
'wp-polyfill',
);
// Note for core merge: only 'wp-edit-blocks' should be in this array.
$style_handles = array(
'wp-edit-blocks',
);
if ( current_theme_supports( 'wp-block-styles' ) ) {
$style_handles[] = 'wp-block-library-theme';
}
if ( 'widgets.php' === $pagenow || 'customize.php' === $pagenow ) {
$style_handles[] = 'wp-widgets';
$style_handles[] = 'wp-edit-widgets';
}
$block_registry = \WP_Block_Type_Registry::get_instance();
foreach ( $block_registry->get_all_registered() as $block_type ) {
// In older WordPress versions, like 6.0, these properties are not defined.
if ( isset( $block_type->style_handles ) && is_array( $block_type->style_handles ) ) {
$style_handles = array_merge( $style_handles, $block_type->style_handles );
}
if ( isset( $block_type->editor_style_handles ) && is_array( $block_type->editor_style_handles ) ) {
$style_handles = array_merge( $style_handles, $block_type->editor_style_handles );
}
if ( isset( $block_type->script_handles ) && is_array( $block_type->script_handles ) ) {
$script_handles = array_merge( $script_handles, $block_type->script_handles );
}
}
$style_handles = array_unique( $style_handles );
$done = wp_styles()->done;
ob_start();
// We do not need reset styles for the iframed editor.
wp_styles()->done = array( 'wp-reset-editor-styles' );
wp_styles()->do_items( $style_handles );
wp_styles()->done = $done;
$styles = ob_get_clean();
$script_handles = array_unique( $script_handles );
$done = wp_scripts()->done;
ob_start();
wp_scripts()->done = array();
wp_scripts()->do_items( $script_handles );
wp_scripts()->done = $done;
$scripts = ob_get_clean();
/*
* Generate font @font-face styles for the site editor iframe.
* Use the registered font families for printing.
*/
if ( class_exists( '\WP_Fonts' ) ) {
$wp_fonts = wp_fonts();
$registered = $wp_fonts->get_registered_font_families();
if ( ! empty( $registered ) ) {
$queue = $wp_fonts->queue;
$done = $wp_fonts->done;
$wp_fonts->done = array();
$wp_fonts->queue = $registered;
ob_start();
$wp_fonts->do_items();
$styles .= ob_get_clean();
// Reset the Web Fonts API.
$wp_fonts->done = $done;
$wp_fonts->queue = $queue;
}
}
return array(
'styles' => $styles,
'scripts' => $scripts,
);
}
}

File diff suppressed because it is too large Load Diff