Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the Preview Menu extensible #31984

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
81d03cb
Allow Preview menu to be extendible by plugins: WIP
mreishus Sep 17, 2020
64cb27d
PluginPreview{MenuItem,}: Change external interface "deviceName" -> "…
mreishus Sep 18, 2020
a690338
Decouple PluginPreviewMenuItem from @wordpress/edit-post
delawski May 12, 2021
bfb8976
Retrieve previewId in parent component
delawski May 18, 2021
36bca61
Accept custom onClick handler and icon in plugin preview
delawski May 18, 2021
5a93322
Update jsdoc blocks
delawski May 18, 2021
d7c329e
Check previewId aka deviceType directly in child component
delawski May 18, 2021
18f2596
Update coreDeviceTypes docs
delawski May 18, 2021
f4e401b
Update slotfills docs
delawski May 18, 2021
f949647
Do not expose menu item fill; simplify PluginPreview component
delawski May 24, 2021
9abd3d0
Allow Preview menu to be extendible by plugins: WIP
mreishus Sep 17, 2020
5907441
PluginPreview{MenuItem,}: Change external interface "deviceName" -> "…
mreishus Sep 18, 2020
0a7d495
Decouple PluginPreviewMenuItem from @wordpress/edit-post
delawski May 12, 2021
cd9a6d1
Retrieve previewId in parent component
delawski May 18, 2021
dc79b50
Accept custom onClick handler and icon in plugin preview
delawski May 18, 2021
866e5c8
Update jsdoc blocks
delawski May 18, 2021
a309d23
Check previewId aka deviceType directly in child component
delawski May 18, 2021
4cd2075
Update coreDeviceTypes docs
delawski May 18, 2021
ee1fba2
Update slotfills docs
delawski May 18, 2021
0301e1d
Do not expose menu item fill; simplify PluginPreview component
delawski May 24, 2021
2236783
Merge branch 'add/preview-menu-slot' of https://github.com/delawski/g…
arcangelini Oct 25, 2021
76c3106
Merge remote-tracking branch 'upstream/trunk' into add/preview-menu-slot
arcangelini Oct 26, 2021
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
@@ -0,0 +1,61 @@
# PluginPreview

Renders the main content area when that menu item is chosen.

A component used to define a custom preview menu item and optional content.

The children of this component will be displayed in the main area of the
block editor, instead of the `VisualEditor` component.

The `title` and `icon` are used to populate the Preview menu item.

## Available Props

- **children** `WPElement`: Preview content.
- **icon** `WPIcon`: Menu item icon to be rendered.
- **name** `string`: A unique name of the custom preview.
- **onClick** `Function`: Menu item click handler, e.g. for previews that provide no content (`children`).
- **title** `string`: Menu item title.

## Example

```js
import { __ } from '@wordpress/i18n';
import { PluginPreview } from '@wordpress/block-editor';
import { registerPlugin } from '@wordpress/plugins';
import { external } from '@wordpress/icons';

const PluginPreviewTest = () => (
<>
<PluginPreview
name="preview-custom-1"
title={ __( 'Custom Preview 1' ) }
>
<h1>
{ __( 'Custom Preview 1 Content' ) }
</h1>
</PluginPreview>

<PluginPreview
name="preview-custom-2"
title={ __( 'Custom Preview 2' ) }
onClick={ ( event ) => console.log( event ) }
>
<h1>
{ __( 'Custom Preview 2 Content' ) }
</h1>
</PluginPreview>

<PluginPreview
name="custom-action"
title={ __( 'Custom Action' ) }
icon={ external }
onClick={ ( event ) => console.log( event ) }
/>
</>
);

registerPlugin( "plugin-preview-test", {
render: PluginPreviewTest,
} );
```
4 changes: 4 additions & 0 deletions docs/reference-guides/slotfills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,7 @@ The following SlotFills are available in the `edit-post` package. Please refer t
- [PluginPrePublishPanel](/docs/reference-guides/slotfills/plugin-pre-publish-panel.md)
- [PluginSidebar](/docs/reference-guides/slotfills/plugin-sidebar.md)
- [PluginSidebarMoreMenuItem](/docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md)

The following SlotFill is available in the `block-editor` package:

- [PluginPreview](/docs/designers-developers/developers/slotfills/plugin-preview.md)
39 changes: 37 additions & 2 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,24 @@ Undocumented declaration.

Undocumented declaration.

### createCustomColorsHOC
<a name="coreDeviceTypes" href="#coreDeviceTypes">#</a> **coreDeviceTypes**

An array of strings that represent `deviceType` values that belong to the
block editor core system.

When the `deviceType` returned by `__experimentalGetPreviewDeviceType()`, is
one of these values, the built-in `VisualEditor` is responsible for rendering
a preview of that type.

When the `deviceType` is something other than one of the `coreDeviceTypes`,
we are rendering a custom preview registered by the `<PluginPreview />`
component and defer to a `<Slot />` filled by the plugin to draw the preview.

_Type_

- `Array`

<a name="createCustomColorsHOC" href="#createCustomColorsHOC">#</a> **createCustomColorsHOC**

A higher-order component factory for creating a 'withCustomColors' HOC, which handles color logic
for class generation color value, retrieval and color attribute setting.
Expand Down Expand Up @@ -520,7 +537,25 @@ _Related_

- <https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/plain-text/README.md>

### PreserveScrollInReorder
<a name="PluginPreview" href="#PluginPreview">#</a> **PluginPreview**

Component used to define a custom preview menu item and optional content.

The children of this component will be displayed in the main area of the
block editor, instead of the `VisualEditor` component.

The `title` and `icon` are used to populate the Preview menu item.

_Parameters_

- _props_ `Object`: Component properties.
- _props.children_ `WPElement`: Preview content.
- _props.icon_ `WPIcon`: Menu item icon to be rendered.
- _props.name_ `string`: A unique name of the custom preview.
- _props.onClick_ `Function`: Menu item click handler, e.g. for previews that provide no content (`children`).
- _props.title_ `string`: Menu item title.

<a name="PreserveScrollInReorder" href="#PreserveScrollInReorder">#</a> **PreserveScrollInReorder**

Undocumented declaration.

Expand Down
6 changes: 5 additions & 1 deletion packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ export { default as withColorContext } from './color-palette/with-color-context'

export { default as __unstableBlockSettingsMenuFirstItem } from './block-settings-menu/block-settings-menu-first-item';
export { default as __unstableInserterMenuExtension } from './inserter-menu-extension';
export { default as __experimentalPreviewOptions } from './preview-options';
export {
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain why __experimentalPreviewOptions and coreDeviceTypes are proposed to become part of public API? They don't seem to be used outside of the package where they are defined.

default as __experimentalPreviewOptions,
coreDeviceTypes,
} from './preview-options';
export { default as PluginPreview } from './plugin-preview';
export { default as __experimentalUseResizeCanvas } from './use-resize-canvas';
export { default as BlockInspector } from './block-inspector';
export { default as BlockList } from './block-list';
Expand Down
78 changes: 78 additions & 0 deletions packages/block-editor/src/components/plugin-preview/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* WordPress dependencies
*/
import {
__experimentalUseSlot as useSlot,
createSlotFill,
Fill,
MenuItem,
} from '@wordpress/components';
import { check } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { coreDeviceTypes } from '../preview-options';

const {
Fill: PluginPreviewMenuFill,
Slot: PluginPreviewMenuSlot,
} = createSlotFill( 'PluginPreviewMenu' );

export { PluginPreviewMenuSlot };

/**
* Component used to define a custom preview menu item and optional content.
*
* The children of this component will be displayed in the main area of the
* block editor, instead of the `VisualEditor` component.
*
* The `title` and `icon` are used to populate the Preview menu item.
*
* @param {Object} props Component properties.
* @param {WPElement} props.children Preview content.
* @param {WPIcon} props.icon Menu item icon to be rendered.
* @param {string} props.name A unique name of the custom preview.
* @param {Function} props.onClick Menu item click handler, e.g. for previews
* that provide no content (`children`).
* @param {string} props.title Menu item title.
*/
export default function PluginPreview( {
Copy link
Member

Choose a reason for hiding this comment

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

As noted in my previous comment, I don't think that this SlotFill should be located inside @wordpress/block-editor as this package contains APIs only related to the block editing interface. This is a higher-level concept that in fact replaces the regular interface you see for editing blocks. That's why I proposed that this functionality goes to @wordpress/interface that already holds similar parts of the header UI like PinnedItems or everything related to ComplementaryArea that is a internal component used by PluginSidebar.

children,
icon,
name,
onClick,
title,
...props
} ) {
const previewSlotName = `core/block-editor/plugin-preview/${ name }`;
const previewSlot = useSlot( previewSlotName );

if ( coreDeviceTypes.includes( name ) ) {
Copy link
Member

Choose a reason for hiding this comment

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

Does it mean that custom plugins can't use the existing names like Tablet?

return null;
}

return (
<>
<PluginPreviewMenuFill>
{ ( { deviceType, setDeviceType } ) => (
<MenuItem
onClick={ ( ...args ) => {
if ( name && previewSlot.fills?.length > 0 ) {
setDeviceType( name );
Copy link
Member

Choose a reason for hiding this comment

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

In the examples provided with the documentation, I see for example preview-custom-1. Is it going to be set as the device type? If that's true then setDeviceType doesn't seem to be a good fit anymore here and it should be replaced with something more general.

}
if ( onClick ) {
onClick( ...args );
}
} }
icon={ icon || ( deviceType === name && check ) }
{ ...props }
>
{ title }
</MenuItem>
) }
</PluginPreviewMenuFill>
{ children && <Fill name={ previewSlotName }>{ children }</Fill> }
</>
);
}
41 changes: 40 additions & 1 deletion packages/block-editor/src/components/preview-options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,44 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { useViewportMatch } from '@wordpress/compose';
import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
import {
__experimentalUseSlot as useSlot,
DropdownMenu,
MenuGroup,
MenuItem,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { check } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { PluginPreviewMenuSlot } from '../plugin-preview';

/**
* An array of strings that represent `deviceType` values that belong to the
* block editor core system.
*
* When the `deviceType` returned by `__experimentalGetPreviewDeviceType()`, is
* one of these values, the built-in `VisualEditor` is responsible for rendering
* a preview of that type.
*
* When the `deviceType` is something other than one of the `coreDeviceTypes`,
* we are rendering a custom preview registered by the `<PluginPreview />`
* component and defer to a `<Slot />` filled by the plugin to draw the preview.
*
* @type {Array}
*/
export const coreDeviceTypes = [ 'Desktop', 'Tablet', 'Mobile' ];

export default function PreviewOptions( {
children,
className,
isEnabled = true,
deviceType,
setDeviceType,
} ) {
const previewMenuSlot = useSlot( PluginPreviewMenuSlot.__unstableName );
const isMobile = useViewportMatch( 'small', '<' );
if ( isMobile ) return null;

Expand Down Expand Up @@ -67,6 +94,18 @@ export default function PreviewOptions( {
{ __( 'Mobile' ) }
</MenuItem>
</MenuGroup>

{ previewMenuSlot.fills?.length > 0 && (
<MenuGroup>
<PluginPreviewMenuSlot
fillProps={ {
deviceType,
setDeviceType,
} }
/>
</MenuGroup>
) }

{ children }
</>
) }
Expand Down
14 changes: 2 additions & 12 deletions packages/edit-post/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
* Internal dependencies
*/
import TextEditor from '../text-editor';
import VisualEditor from '../visual-editor';
import VisualEditorOrPluginPreview from '../visual-editor/visual-editor-or-plugin-preview';
import EditPostKeyboardShortcuts from '../keyboard-shortcuts';
import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal';
import PreferencesModal from '../preferences-modal';
Expand Down Expand Up @@ -85,8 +85,6 @@ function Layout( { styles } ) {
showIconLabels,
hasReducedUI,
showBlockBreadcrumbs,
isTemplateMode,
documentLabel,
} = useSelect( ( select ) => {
const { getEditorSettings, getPostTypeLabel } = select( editorStore );
const editorSettings = getEditorSettings();
Expand Down Expand Up @@ -125,8 +123,6 @@ function Layout( { styles } ) {
showBlockBreadcrumbs: select( editPostStore ).isFeatureActive(
'showBlockBreadcrumbs'
),
// translators: Default label for the Document in the Block Breadcrumb.
documentLabel: postTypeLabel || _x( 'Document', 'noun' ),
};
}, [] );
const className = classnames( 'edit-post-layout', 'is-mode-' + mode, {
Expand Down Expand Up @@ -228,13 +224,7 @@ function Layout( { styles } ) {
<TextEditor />
) }
{ isRichEditingEnabled && mode === 'visual' && (
<VisualEditor styles={ styles } />
) }
{ ! isTemplateMode && (
<div className="edit-post-layout__metaboxes">
<MetaBoxes location="normal" />
<MetaBoxes location="advanced" />
</div>
<VisualEditorOrPluginPreview styles={ styles } />
Copy link
Member

Choose a reason for hiding this comment

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

It feels like PluginPreview should be independent of the mode. Well, maybe at least it should still be possible to see the preview when isRichEditingEnabled is disabled. It needs some more thinking because depending on the content of the preview, it might not play with how we use the visual editor today.

Could you disable rich editing, use text mode to edit content and still use the custom preview from the plugin?

) }
{ isMobileViewport && sidebarIsOpened && (
<ScrollLock />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* WordPress dependencies
*/
import { __experimentalUseSlot as useSlot, Slot } from '@wordpress/components';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import VisualEditor from './index';
import { store as editPostStore } from '../../store';

/**
* Component that renders a preview slot fill if found or a VisualEditor instead.
*
* @param {Object} props Component properties.
*/
function VisualEditorOrPluginPreview( props ) {
const previewId = useSelect(
( select ) =>
select( editPostStore ).__experimentalGetPreviewDeviceType(),
[]
);
const previewSlotName = `core/block-editor/plugin-preview/${ previewId }`;
const previewSlot = useSlot( previewSlotName );

if ( previewSlot.fills?.length > 0 ) {
return <Slot name={ previewSlotName } fillProps={ props } />;
}

return <VisualEditor { ...props } />;
}
export default VisualEditorOrPluginPreview;