Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SlotFillProvider, Popover } from '@wordpress/components';
/**
* Internal dependencies
*/
import ErrorBoundary from '../error-boundary';
import SidebarBlockEditor from '../sidebar-block-editor';
import FocusControl from '../focus-control';
import SidebarControls from '../sidebar-controls';
Expand Down Expand Up @@ -42,13 +43,15 @@ export default function CustomizeWidgets( {
const activeSidebar =
activeSidebarControl &&
createPortal(
<SidebarBlockEditor
key={ activeSidebarControl.id }
blockEditorSettings={ blockEditorSettings }
sidebar={ activeSidebarControl.sidebarAdapter }
inserter={ activeSidebarControl.inserter }
inspector={ activeSidebarControl.inspector }
/>,
<ErrorBoundary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add this to the root of the editor, in hope of being able to correctly reset all the states. Not sure if it would work though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave it a try during development, but yeah, doesn't quite work. I think we'll need something a little different to handle the portals properly in the customizer editor.

Copy link
Member

@kevin940726 kevin940726 Aug 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a problem with the portals, but rather that we(I) don't properly clear/reset the states of controls after unmounted 😅. It didn't occur to me that we would ever need to unmount the component. Probably just have to add some useEffect in the components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error boundary UI also needs to render in the right place (which is probably in the portal).

7d607ff has my first attempt to solve the activeSidebarControl state problems, but it was a bit naive because even the context is lost when unmounting/remounting the app.

Moving the active sidebar control to store state so that it persists across umounting/remounting the whole application might be the easiest option.

<SidebarBlockEditor
key={ activeSidebarControl.id }
blockEditorSettings={ blockEditorSettings }
sidebar={ activeSidebarControl.sidebarAdapter }
inserter={ activeSidebarControl.inserter }
inspector={ activeSidebarControl.inspector }
/>
</ErrorBoundary>,
activeSidebarControl.container[ 0 ]
);

Expand Down
50 changes: 50 additions & 0 deletions packages/customize-widgets/src/components/error-boundary/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { Warning } from '@wordpress/block-editor';
import { useCopyToClipboard } from '@wordpress/compose';

function CopyButton( { text, children } ) {
const ref = useCopyToClipboard( text );
return (
<Button variant="secondary" ref={ ref }>
{ children }
</Button>
);
}

export default class ErrorBoundary extends Component {
constructor() {
super( ...arguments );
this.state = {
error: null,
};
}

componentDidCatch( error ) {
this.setState( { error } );
}

render() {
const { error } = this.state;
if ( ! error ) {
return this.props.children;
}

return (
<Warning
className="customize-widgets-error-boundary"
actions={ [
<CopyButton key="copy-error" text={ error.stack }>
{ __( 'Copy Error' ) }
</CopyButton>,
] }
>
{ __( 'The editor has encountered an unexpected error.' ) }
</Warning>
);
}
}
64 changes: 64 additions & 0 deletions packages/edit-widgets/src/components/error-boundary/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { Warning } from '@wordpress/block-editor';
import { useCopyToClipboard } from '@wordpress/compose';

function CopyButton( { text, children } ) {
const ref = useCopyToClipboard( text );
return (
<Button variant="secondary" ref={ ref }>
{ children }
</Button>
);
}

export default class ErrorBoundary extends Component {
constructor() {
super( ...arguments );

this.reboot = this.reboot.bind( this );

this.state = {
error: null,
};
}

componentDidCatch( error ) {
this.setState( { error } );
}

reboot() {
this.props.onError();
}

render() {
const { error } = this.state;
if ( ! error ) {
return this.props.children;
}

return (
<Warning
className="edit-widgets-error-boundary"
actions={ [
<Button
key="recovery"
onClick={ this.reboot }
variant="secondary"
>
{ __( 'Attempt Recovery' ) }
</Button>,
<CopyButton key="copy-error" text={ error.stack }>
{ __( 'Copy Error' ) }
</CopyButton>,
] }
>
{ __( 'The editor has encountered an unexpected error.' ) }
</Warning>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.edit-widgets-error-boundary {
margin: auto;
max-width: 780px;
padding: 20px;
margin-top: 60px;
box-shadow: $shadow-modal;
}
25 changes: 14 additions & 11 deletions packages/edit-widgets/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,27 @@ import { PluginArea } from '@wordpress/plugins';
/**
* Internal dependencies
*/
import ErrorBoundary from '../error-boundary';
import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider';
import Sidebar from '../sidebar';
import Interface from './interface';
import UnsavedChangesWarning from './unsaved-changes-warning';
import WelcomeGuide from '../welcome-guide';

function Layout( { blockEditorSettings } ) {
function Layout( { blockEditorSettings, onError } ) {
return (
<WidgetAreasBlockEditorProvider
blockEditorSettings={ blockEditorSettings }
>
<Interface blockEditorSettings={ blockEditorSettings } />
<Sidebar />
<Popover.Slot />
<PluginArea />
<UnsavedChangesWarning />
<WelcomeGuide />
</WidgetAreasBlockEditorProvider>
<ErrorBoundary onError={ onError }>
<WidgetAreasBlockEditorProvider
blockEditorSettings={ blockEditorSettings }
>
<Interface blockEditorSettings={ blockEditorSettings } />
<Sidebar />
<Popover.Slot />
<PluginArea />
<UnsavedChangesWarning />
<WelcomeGuide />
</WidgetAreasBlockEditorProvider>
</ErrorBoundary>
);
}

Expand Down
25 changes: 22 additions & 3 deletions packages/edit-widgets/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase
setFreeformContentHandlerName,
} from '@wordpress/blocks';
import { render } from '@wordpress/element';
import { render, unmountComponentAtNode } from '@wordpress/element';
import {
registerCoreBlocks,
__experimentalGetCoreBlocks,
Expand Down Expand Up @@ -36,13 +36,32 @@ const disabledBlocks = [
...( ! ALLOW_REUSABLE_BLOCKS && [ 'core/block' ] ),
];

/**
* Reinitializes the editor after the user chooses to reboot the editor after
* an unhandled error occurs, replacing previously mounted editor element using
* an initial state from prior to the crash.
*
* @param {Element} target DOM node in which editor is rendered.
* @param {?Object} settings Editor settings object.
*/
export function reinitializeEditor( target, settings ) {
unmountComponentAtNode( target );
const reboot = reinitializeEditor.bind( null, target, settings );
render(
<Layout blockEditorSettings={ settings } onError={ reboot } />,
target
);
}

/**
* Initializes the block editor in the widgets screen.
*
* @param {string} id ID of the root element to render the screen in.
* @param {Object} settings Block editor settings.
*/
export function initialize( id, settings ) {
const target = document.getElementById( id );
const reboot = reinitializeEditor.bind( null, target, settings );
const coreBlocks = __experimentalGetCoreBlocks().filter( ( block ) => {
return ! (
disabledBlocks.includes( block.name ) ||
Expand Down Expand Up @@ -70,8 +89,8 @@ export function initialize( id, settings ) {
// see: https://github.com/WordPress/gutenberg/issues/33097
setFreeformContentHandlerName( 'core/html' );
render(
<Layout blockEditorSettings={ settings } />,
document.getElementById( id )
<Layout blockEditorSettings={ settings } onError={ reboot } />,
target
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/edit-widgets/src/style.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import "../../interface/src/style.scss";

@import "./blocks/widget-area/editor.scss";
@import "./components/error-boundary/style.scss";
@import "./components/header/style.scss";
@import "./components/keyboard-shortcut-help-modal/style.scss";
@import "./components/more-menu/style.scss";
Expand Down