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

Add an API to add a plugin sidebar (new) #4777

Merged
merged 22 commits into from
Feb 21, 2018
Merged

Add an API to add a plugin sidebar (new) #4777

merged 22 commits into from
Feb 21, 2018

Conversation

IreneStr
Copy link
Member

@IreneStr IreneStr commented Jan 31, 2018

Description

This adds an API that plugins can use to add sidebars.

The API looks like this:

wp.editPost.registerSidebar( 
	"my-plugin/my-sidebar", 
	{
		title: "Name",
		render: () => { return <div>My sidebar</div>;}
	} 
);
wp.editPost.activateSidebar( "my-plugin/my-sidebar" );

How Has This Been Tested?

There is a PR on Yoast SEO to test this with. Yoast/wordpress-seo#8531 shows how the API can be used.

Screenshots (jpeg or gifs if applicable):

schermafbeelding 2018-02-14 om 14 29 35

Types of changes

New API

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code has proper inline documentation.

Remarks

  • When triggering a plugin sidebar from outside Gutenberg (see the Yoast branch mentioned by @atimmer above), there is a timing issue. Sometimes the activation happens before the registration, which results in an error No matching plugin sidebar found for plugin [plugin name]. When refreshing the page with the sidebar opened, the same error will appear.
  • Because of we're handling sidebars differently now, mobileMiddleware is no longer needed. Whether the mobile sidebar should be opened is now based on a viewport property in the state (it used to be based on sidebar: 'mobile' in the state). I've removed the mobile middleware in the edit-post folder. It would be great if someone can double check whether I didn't break anything related to that.
  • The sidebar is hidden when you change from desktop to mobile. However, it's not saved that it was opened before, which means it will not be reopened when switching back to desktop.

For more comments, see #4109

@IreneStr IreneStr mentioned this pull request Jan 31, 2018
3 tasks
@aduth aduth added this to the Feature Complete milestone Jan 31, 2018
@aduth aduth added Framework Issues related to broader framework topics, especially as it relates to javascript [Feature] Extensibility The ability to extend blocks or the editing experience labels Jan 31, 2018
@jasmussen
Copy link
Contributor

jasmussen commented Feb 8, 2018

I think I said this on the last PR, but I'll say it again: nice work — I think developers will love this API.

Keep in mind I can only speak for the UI and user experienceon this, so take any thoughts from me in that vein.

The principles are these:

  • A plugin can register a sidebar
  • This sidebar can be opened from the More menu, if the user clicks the item there, or a toolbar icon button, if the sidebar is pinned
  • A toolbar icon is pinned, but can be unpinned. I previously suggested that a plugin can't itself pin the icon, but the user has to do this. However per the consensus in our recent slack chats, let's double back on that, and let a plugin add a toolbar icon if it wants to. But the user should still be able to unpin that icon (see the star bookmark button in older mockups).
  • A sidebar with an unpinned icon can be opened from the More menu item.
  • A pinned icon is a single-color inline SVG icon

As also mentioned in a previous PR, not all of these principles have to be part of THIS PR. It's totally fine that we address these later on. But I'm explicitly stating these so we can have some agreement as to where this should end up.

If we agree this is the approach to pursue and this will be possible and desirable, then I think UI wise this PR is good to go after two quick visual fixes. The plugin sidebar should look a bit more like the standard sidebar:

screen shot 2018-02-08 at 09 53 21

Right now the plugin sidebar has a bigger font size in the title (should be font-size: 100%;), the titlebar is 56px, should be 50px, and the close icon is right-aligned, should have a 10px right margin. In other words this is how it should look:

screen shot 2018-02-08 at 09 55 20

With those tiny visual tweaks, and an agreement as to where to go next, 👍 👍 from me on the visuals and interaction!

@jasmussen
Copy link
Contributor

Another quick note I forgot to mention — the behavior for the sidebar hiding and not restoring when you resize the screen has regressed from master. We should ideally restore it like we do in master.

We also need to be sure that the mobile experience is good, the modal sidebar system has regressed also:

screen_shot_2018-02-08_at_12 17 42

But modals on mobile need imminent polish regardless, as part of #4082, so perhaps that doesn't need to be addressed in this PR. The thing is — due to how WordPress handles fixed and sticky toolbars at various breakpoints, and due to the need to handle mobile scroll bleed (don't scroll content underneath the modal when you're swiping, and also don't lose your place when you open a modal), it's difficult to get the modals to behave well here. I know @youknowriad has a component that takes some of the vinegar out.

* @return {string} Active plugin sidebar plugin
*/
export function getActivePlugin( state ) {
return getPreference( state, 'activeSidebarPanel', {} ).plugins;
Copy link
Member

Choose a reason for hiding this comment

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

Minor: in the state tree we save as "plugins", but the selector uses "Plugin". Should we allow more than one plugin to be active? If yes the selector should be renamed to getActivePlugins if not the property should be renamed to plugin.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jorgefilipecosta That's a good question. I think because this function is sidebar specific (using 'activeSidebarPanel' in the getPreference function), there could only be one plugin active at any given time. I'll rename the property accordingly.

export default connect(
( state ) => {
return {
plugin: getActivePlugin( state ),
Copy link
Member

@jorgefilipecosta jorgefilipecosta Feb 8, 2018

Choose a reason for hiding this comment

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

It looks like this will always return null (and we will see the 'No matching plugin sidebar found for plugin ""'), because the store being queried here is not the store that contains the active ActiveEditorPanel preference.
We can confirm that by adding console.log( 'PluginsPanel' , state ); before the return.
We can correct that by specifying the store key:

export default connect(
	( state ) => {
		return {
			plugin: getActivePlugin( state ),
		};
	}, {
		onClose: closeGeneralSidebar,
	}, undefined,
	{ storeKey: 'edit-post' }
)( withFocusReturn( PluginsPanel ) );

Copy link
Member

@jorgefilipecosta jorgefilipecosta left a comment

Choose a reason for hiding this comment

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

It looks like the changes, to the mobile sidebar, hiding/persisting behavior are good, nice job in that refactoring 👍

publish: false,
editorMode: 'visual',
viewportType: 'desktop', // 'desktop' | 'mobile'
activeGeneralSidebar: null, // null | 'editor' | 'plugins'
Copy link
Member

@jorgefilipecosta jorgefilipecosta Feb 8, 2018

Choose a reason for hiding this comment

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

I think here we should set activeGeneralSidebar to 'editor', otherwise, we are introducing a behavior change because when reloading the page without preferences stored in localStorage no sidebar will be active.

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

Thank you for your work on this PR. It looks like we are pretty close to have it merged.

I think we still need to clarify how the public API should look like. I left some question regarding renderSidebar and getSidebarSettings methods exposed. It seems like registerSidebar would be a better fit here. I'm wondering what's your take on this and why it has changed in this new PR.

I played with the code locally and I saw some issues:

screen shot 2018-02-08 at 11 58 40

I think @jorgefilipecosta found the reason of this issue. We need to fix it before merging this PR. The screenshot I shared reveals also the issue with the renderSidebar method - which registers the sidebar every time it's going to be executed. This needs to be optimized.

I have a feeling that it might be possible to reuse or extract more functionality out of the existing sidebar components. This might be done in the follow-up PRs, we just should identify what would be the best way to move forward.

Another thing to think about in the future (not for this PR) is if we could handle the global settings and block settings as sidebar plugins, too. This would allow us further simplify the way we handle state because we wouldn't have to worry about two competing sidebars which want to be displayed in the same area.

Let's make sure there are no unexpected behaviors and come up with the plan how to continue work on this feature.

*/
function activateSidebar( name ) {
store.dispatch( openGeneralSidebar( 'plugins' ) );
store.dispatch( setGeneralSidebarActivePanel( 'plugins', name ) );
Copy link
Member

Choose a reason for hiding this comment

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

Would be it possible to dispatch one action named after the function (activateSidebar) to update all reducers that are involved in this state update?
Isn't meant to be dispatched directly inside the menu component in the future? What is the reason for having it outside of the component's lifecycle?

Copy link
Member Author

Choose a reason for hiding this comment

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

This will be addressed in a next iteration

@@ -0,0 +1,4 @@
export {
renderSidebar,
getSidebarSettings,
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 give an example of the use case where getSidebarSettings would be useful for plugin developers? It not used in the code as part of this API. I don't see also any comments in the PRs description. It would be nice to have a better overview on how this API might work.

Copy link
Member Author

Choose a reason for hiding this comment

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

The exposure of getSidebarSettings was a remnant of some debugging. I'll remove it from the API. I cannot think of a reason to leave it exposed. When someone thinks of a use case it can be exposed again.

return null;
}

settings = applyFilters( 'editor.registerSidebar', settings, name );
Copy link
Member

Choose a reason for hiding this comment

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

I think we have the same logic in blocks.registerBlockType. I'm afraid this needs to be further discussed. The thing is that it happens after validation process is done, so you can update all settings however you want and it won't be validated again. It seems like this should happen before the validation begins.

Copy link
Member Author

Choose a reason for hiding this comment

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

This will be addressed in a next iteration

@@ -0,0 +1,4 @@
export {
renderSidebar,
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 previous version, we had registerSidebar exposed in the public API. What was the reason to use renderSidebar instead?

*
* @return {void}
*/
export function renderSidebar( name, settings ) {
Copy link
Member

Choose a reason for hiding this comment

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

When I call this method twice for the same name, I see the following error on the console:

Sidebar my-plugin/my-sidebar is already registered.

This is exposed as public API. I assume this is what would be executed from the menu, so it shouldn't register the same sidebar every time a user clicks on the menu item.

/>
</div>
<div className="editor-plugins-panel__content">
{ render() }
Copy link
Member

Choose a reason for hiding this comment

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

This won't work properly in the case where render property will contain Component class. It should follow React guidelines here: Composing Components.

<div>
    <RenderComponent />
</div>

Copy link
Member

@gziolo gziolo Feb 8, 2018

Choose a reason for hiding this comment

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

On second thought I don't think we need to change that. It is perfectly fine as long as we make it clear that you need to return WPElement from the function, similar to what createElement produces:

class MyComponent extends Component {
    render() {
        return <div>My Component</div>
    }
}
const render = () => <MyComponent />;

render,
} = getPluginSidebar( plugin );
return (
<div
Copy link
Member

Choose a reason for hiding this comment

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

This component is almost identical to <Sidebar /. It would be nice to reuse it here. See:
https://github.com/WordPress/gutenberg/blob/master/edit-post/components/sidebar/index.js#L24

Copy link
Member Author

@IreneStr IreneStr Feb 13, 2018

Choose a reason for hiding this comment

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

Since <Header/> is nested inside <Sidebar/>, this would be something for a next iteration as well (see comment below https://github.com/WordPress/gutenberg/pull/4777/files/3cdf86f520f8f379b50eeed62872e24fb7516b76#r167880008)

tabIndex="-1">
<div className="editor-plugins-panel__header">
<h3>{ title }</h3>
<IconButton
Copy link
Member

Choose a reason for hiding this comment

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

There is also <Header /> component which looks similar to what we have here: https://github.com/WordPress/gutenberg/blob/master/edit-post/components/sidebar/header.js#L41. For instance it contains an identical close button. Is it possible to make those components more flexible so we could use them in here, too?

Copy link
Member Author

Choose a reason for hiding this comment

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

The close button is the only similarity. The header in sidebar/header.js solely consists of three buttons. The header component may be generalized to fit both headers, but that's something for a next iteration.

import { closeGeneralSidebar, closePublishSidebar } from '../../store/actions';
import PluginsPanel from '../../components/plugins-panel/index.js';

function GeneralSidebar( { openedGeneralSidebar } ) {
Copy link
Member

Choose a reason for hiding this comment

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

This might be inlined inside the component that is consuming it. If it is going to be reused in other places it should be moved to its own file.

{ publishSidebarOpen && <PostPublishPanel onClose={ onClosePublishSidebar } /> }
{
openedGeneralSidebar !== null && <GeneralSidebar
onCloseGeneralSidebar={ onCloseGeneralSidebar }
Copy link
Member

Choose a reason for hiding this comment

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

Both props passed down aren't propagated to the child components.

@jorgefilipecosta
Copy link
Member

jorgefilipecosta commented Feb 8, 2018

We are persisting the active plugin panel, so if when we reload because of some reason the sidebar is not available (e.g: plugin was uninstalled) we immediately enter into an error state. As an improvement when loading the preferences from localStorage we should check if the sidebar is available before showing the error.

@jorgefilipecosta
Copy link
Member

Thank you a lot for the work in this PR, I left some suggestions about possible improvements/bugfixes but generally, it looks like we are getting close to a merge :)

@@ -8,7 +8,6 @@ import { registerReducer, withRehydratation, loadAndPersist } from '@wordpress/d
*/
import reducer from './reducer';
import enhanceWithBrowserSize from './mobile';
import applyMiddlewares from './middlewares';
import { BREAK_MEDIUM } from './constants';
Copy link
Member

@gziolo gziolo Feb 14, 2018

Choose a reason for hiding this comment

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

We don't need ./middlewares file anymore if we stop using it here.

@youknowriad can you confirm we can remove it completely?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, it's a local dependency if the mobile middleware is gone we can safely remove it. but how is the mobile Middleware replaced right now? Sorry didn't review in depth?

Copy link
Member

Choose a reason for hiding this comment

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

The need for mobile middleware was removed because of a logic was changed. As a side effect:

The sidebar is hidden when you change from desktop to mobile. However, it's not saved that it was opened before, which means it will not be reopened when switching back to desktop.

I think it is ok to remove that behavior as the reopen from mobile to desktop was not an initial requirement. And we can try to propose a solution for that if required after this PR is merged.

@gziolo
Copy link
Member

gziolo commented Feb 19, 2018

When I register my sidebar and activate it, then refresh the page I still see this error about missing plugin. I think it needs to be fixed, because it is expected that this sidebar shouldn't be there, so this error is only confusing for the user:

screen shot 2018-02-19 at 16 42 26

@jasmussen
Copy link
Contributor

While I really want this to get merged in, I would object to the merger until these UI issues are fixed. Specifically:

  • The plugin sidebar header is too tall compared to the post settings sidebar header
  • The font is larger compared to the post settings sidebar header
  • The X close button is offset compared to the post settings sidebar header

My primary concern is that we merge this in and work on addressing the UI glitches falls through the cracks, forgotten mostly because you'd only ever see it if you also installed a plugin that took advantage of this new API, which very few would do.

@gziolo gziolo mentioned this pull request Feb 19, 2018
3 tasks
@jorgefilipecosta
Copy link
Member

The improvements I referred in the last review are addressed, thank you for applying the fixes.
The error @gziolo referred is caused by the plugin sidebar not being available when we reload. I referred this problem and said it may be future work because it probably happens when plugins are removed, but I was not aware of all the impact it may have in other cases e.g: plugin renaming sidebars.
So if we can add a verification to check if the sidebar is available when loading the preferences from local storage instead of showing the error, solving the reload issues @gziolo referred it would be nice.
Thank you for your contribution 👍

@IreneStr
Copy link
Member Author

IreneStr commented Feb 20, 2018

@jasmussen My bad. I was under the impression that I had fixed those UI things when I did a CSS review and removed duplicate CSS, which wasn't the case. I've just pushed more CSS changes to address the issues you raised.

@jorgefilipecosta @gziolo I'll address your feedback tomorrow.

@@ -12,6 +12,10 @@

h3 {
margin: 0;
font-weight: 400;
color: inherit;
Copy link
Contributor

Choose a reason for hiding this comment

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

Haven't had time to check, but should the font size also be addressed? The font size should be 13px, and you get that from the $default-font-size variable here https://github.com/WordPress/gutenberg/blob/master/edit-post/assets/stylesheets/_variables.scss#L7

Copy link
Member Author

Choose a reason for hiding this comment

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

schermafbeelding 2018-02-21 om 09 57 55

@jasmussen The current font size is 13px. Or do I need to set it explicitly for some reason?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah right, sorry, wasn't able to test. No that looks good. Though feel free to set the font weight to 600 so it's bold, like an active tab. Otherwise 👍 👍

@gziolo
Copy link
Member

gziolo commented Feb 21, 2018

I added a few commits to remove the sidebar state with error messages. Let's tackle it separately in a way that is less confusing for the users, but at the same time helpful for site owners so they could get proper feedback.

screen shot 2018-02-19 at 16 42 26

At this moment when there is no matching sidebar registered by Redux has active plugin stored, we stop rendering sidebar. Let's iterate on this in the follow-up PR.

@gziolo
Copy link
Member

gziolo commented Feb 21, 2018

There is one known issue. Steps to reproduce:

  • register the sidebar and activate it
  • reload the page
  • it gets closed as expected
  • if then we register the sidebar again and activate it, it does not show up as expected

This is not likely that user will get into such flow, so I think we can fix it in a follow-up PR.

@IreneStr thanks for working on this one. It looks solid and we can proceed. We agreed that it's better to merge it in the current shape and address all the comments in the smaller targeted PRs. ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Extensibility The ability to extend blocks or the editing experience Framework Issues related to broader framework topics, especially as it relates to javascript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants