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:
parent
4a6b5ac0cd
commit
ebe879d5dd
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add iframe block editor
|
|
@ -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:*",
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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%;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './iframe-editor';
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
@import './iframe-editor.scss';
|
||||
@import './resize-handle.scss';
|
|
@ -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';
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add unresolved assets for iframe editors to editor settings
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
1393
pnpm-lock.yaml
1393
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue