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

Plugins: Add pinning support for sidebar plugins #6442

Merged
merged 14 commits into from
May 14, 2018
Merged

Conversation

gziolo
Copy link
Member

@gziolo gziolo commented Apr 26, 2018

Description

This PR adds pinning support for sidebar plugins as described by @jasmussen in #4287 (comment):

screen shot 2018-04-18 at 14 19 18

☝️ this features a "pin" icon, allowing you to pin or unpin this. It's quick and dirty, would like to polish it more, but it conveys the point.

To recap the ideas for this kind of extensibility right now, they are:

  • Every plugin that extends the editor gets a menu item in the more menu. Invoking this item invokes the plugins primary action.
  • Plugin actions could be to open a sidebar, or to open a screen takeover, or to run an action (such as spell check) directly.
  • Plugins can register a pinned extension icon next to the cog but before the more menu. These are plain svg icons that are recolored to be solid gray.
  • A user can unpin any extension icon that a plugin has pinned. The user can always use an unpinned plugin because the action will always be available in the more menu.
  • The way to unpin (or pin) an extension icon is currently the weakest aspect of the mockups, but right now they have you tap a star to toggle pinning, hollow for unpinned, solid for pinned.

and also in #3330 (comment):

Here are some mockups for how pinning extensions to the toolbar could work. This is inspired by Chrome and Firefox.

  1. You open it from the ellipsis, where all editor extensions register themselves

wolframalpha open from ellipsis

  1. This immediately opens a sidebar, because this is a "editor sidebar extension":

wolframalpha sidebar open

  1. All sidebar extensions have a "Star" icon in their heading.

wolframalpha pin to toolbar

  1. Click it to pin the icon to the toolbar:

wolframalpha extension pinned

  1. Even if you close the sidebar, the extension icon still sits there for quick access:

pinned

Repeat these steps in reverse to unpin it.

How has this been tested?

It was tested manually using a test plugin. The easiest way to apply the plugin is to copy ES5 version from this gist: https://gist.github.com/gziolo/e2954aa83aa1f823b2b05ca1660c2223.

Screenshots

Version 1 - always present 2 - unpinned by default 3 - pinned by default

pinned plugins

Types of changes

New feature (non-breaking change which adds functionality).

Checklist:

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

@gziolo gziolo added [Type] Task Issues or PRs that have been broken down into an individual action to take [Status] In Progress Tracking issues with work in progress [Feature] Extensibility The ability to extend blocks or the editing experience labels Apr 26, 2018
@gziolo gziolo self-assigned this Apr 26, 2018
@gziolo gziolo requested a review from jasmussen April 26, 2018 12:01
aria-expanded={ isEditorSidebarOpened }
/>
<MoreMenu key="more-menu" />
<PinnedPlugins>
Copy link
Member Author

Choose a reason for hiding this comment

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

Question is if we should also convert Document Settings into a plugin. I put this fill in here as a temporary solution.

Copy link
Member Author

Choose a reason for hiding this comment

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

Out of scope for this PR.

@paulwilde
Copy link
Contributor

paulwilde commented Apr 26, 2018

It would be nice if the end-user had the ability to remove these pins, as it would become quite cluttered (especially on mobile) after installing a few plugins. Almost to the point of being reminiscent of Internet Explorer toolbars that you cannot get rid of.

Personally I'd prefer them to be opt-in and not forced by default.

@jasmussen
Copy link
Contributor

Yep, this has been an ongoing topic for a while, and there are more details on the native extensibility ticket.

The current plan is opt out. That is, a plugin can choose to pin an icon as soon as the plugin is activated. The user can then opt out of that pinned icon by clicking it and removing the star.

Every plugin has a menu item that opens the same as the pinned icon does.

A previous plan had this be opt in, as in you had to open the plugin from the more menu first, and then explicitly pin it by checking the star.

@jasmussen
Copy link
Contributor

Personally I'd prefer them to be opt-in and not forced by default.

Worth explicitly stating, in case there's any doubt: a plugin doesn't have to register a pinned icon.

In that way this is similar to top level admin menus.

@gziolo gziolo added this to the 2.9 milestone May 7, 2018
@gziolo gziolo force-pushed the add/sidebar-pinning branch from 80b02c8 to 8dc4fa2 Compare May 7, 2018 13:38
@gziolo gziolo removed the [Status] In Progress Tracking issues with work in progress label May 7, 2018
@gziolo gziolo requested a review from karmatosed May 7, 2018 14:12
@gziolo gziolo added the Needs Design Feedback Needs general design feedback. label May 7, 2018
@gziolo gziolo changed the title [WIP] Plugins: Add pinning support for sidebar plugins Plugins: Add pinning support for sidebar plugins May 7, 2018
@gziolo
Copy link
Member Author

gziolo commented May 7, 2018

I have an initial implementation ready to review.

Worth explicitly stating, in case there's any doubt: a plugin doesn't have to register a pinned icon.

Every plugin item (sidebar, takeover screen, etc) - can opt out from being pinnable by using pinnable={ false } prop when composing plugin's components.

The current plan is opt out. That is, a plugin can choose to pin an icon as soon as the plugin is activated. The user can then opt out of that pinned icon by clicking it and removing the star.

I started with the easier implementation or more straightforward - everything is unpinned by default. Once the user pins (and unpins afterward), this state is persisted to be used during the next visits.

A previous plan had this be opt in, as in you had to open the plugin from the more menu first, and then explicitly pin it by checking the star.

I will look into it tomorrow. However, I need to find a way how to prevent displaying the icon for the plugin item that opted out from being pinnable.

@gziolo gziolo requested a review from a team May 7, 2018 14:45
@noisysocks
Copy link
Member

Code-wise this looks great 👍

The only bug I'm noticing is that a pinned item can become stuck if the plugin is updated:

  1. Pin the custom sidebar
  2. Change the custom sidebar to have pinnable={ false }
  3. Refresh the page. The custom sidebar is still pinned and you cannot unpin it

@jasmussen
Copy link
Contributor

Not entirely sure how to test this yet, but from your process and the screenshots, this looks right on the money. Thanks for working on this.

I think it's important that we try and get this in as soon as we possibly can — not to add stress :) — it would be good to start testing the current plan which is to allow extensions to pin themselves by default and requiring the user to unpin them if they would prefer them in the More Menu only.

@gziolo
Copy link
Member Author

gziolo commented May 8, 2018

I think it's important that we try and get this in as soon as we possibly can — not to add stress :) — it would be good to start testing the current plan which is to allow extensions to pin themselves by default and requiring the user to unpin them if they would prefer them in the More Menu only.

Taking into account what @noisysocks found out, I think I need to refactor code in a way which will allow to make the proposed flow to be the default one. I have a hunch that it can be achieved by moving Fill for pins next to the sidebar rather than having it bundled together with the menu item. Initially, it seemed to be an easier solution because all the logic for both the menu item and pin are the same.

Not entirely sure how to test this yet, but from your process and the screenshots, this looks right on the money. Thanks for working on this.

The easiest way to test it is to copy this file and paste it in the JS console. It will enable the sidebar. I also added a second variation with the palmtree icon to test that pins are always ordered the same way. When you refresh the page, you will have to paste this code again and pins should appear back even though you won't see them on the initial load.

@jasmussen
Copy link
Contributor

Awesome, thank you for the code. Yes absolutely address Roberts' comments, didn't mean to add stress. I'm just excited to see this, as it's like a bow on our extensibility efforts.

@gziolo gziolo force-pushed the add/sidebar-pinning branch 2 times, most recently from 4427b83 to fef693b Compare May 8, 2018 17:05
@gziolo
Copy link
Member Author

gziolo commented May 8, 2018

I think I have it all sorted out:

pinned plugins

@gziolo
Copy link
Member Author

gziolo commented May 8, 2018

The only bug I'm noticing is that a pinned item can become stuck if the plugin is updated:

  1. Pin the custom sidebar
  2. Change the custom sidebar to have pinnable={ false }
  3. Refresh the page. The custom sidebar is still pinned and you cannot unpin it

@noisysocks - it should work properly with the recent changes introduced.


- Type: `String` | `Element`
- Required: No
- Default: `admin-plugins`
Copy link
Member Author

Choose a reason for hiding this comment

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

@jasmussen - should I pick something else as default? I assumed the more generic icon, the higher probability it gets replaced by the plugin author :)

Copy link
Member

@noisysocks noisysocks left a comment

Choose a reason for hiding this comment

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

Code looks good. Tested locally and everything seems peachy. If Joen is happy, I'm happy.

const { Fill: PinnedPlugins, Slot } = createSlotFill( 'PinnedPlugins' );

PinnedPlugins.Slot = ( { fillProps } ) => (
<Slot fillProps={ fillProps }>
Copy link
Member

Choose a reason for hiding this comment

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

minor: We can make this more future-proof by passing every prop to <Slot>. Then, if the API for Slot ever changes, PinnedPlugins.Slot will change with it.

PinnedPlugins.Slot = ( props ) => (
    <Slot { ...props }>

icon = 'admin-plugins',
isActive,
isPinned,
pinnable = true,
Copy link
Member

Choose a reason for hiding this comment

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

minor: Would isPinnable be more consistent with e.g. isActive and isPinned?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, makes sense.

label={ title }
onClick={ toggleSidebar }
isToggled={ isActive }
aria-expanded={ isActive }
Copy link
Member

Choose a reason for hiding this comment

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

irrelevant: Should Button handle setting aria-expanded iff isToggled is set?

Copy link
Member Author

@gziolo gziolo May 9, 2018

Choose a reason for hiding this comment

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

Good idea, I will double check all occurrences and update IconButton and if it is applicable I will fix in a follow-up PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

By https://www.w3.org/WAI/GL/wiki/Using_aria-expanded_to_indicate_the_state_of_a_collapsible_element:

When authors use collapsible content, for example, to hide navigation menus or lists of content, the triggering link or button should indicate to screen reader users whether the collapsable content below is in the expanded or in the collapsed state. The aria-expanded attribute is used for this purpose.

It looks like it isn't always the case that both those props should exist. See:
https://github.com/WordPress/gutenberg/blob/master/components/date-time/time.js#L145-L158
In this case, buttons can be toggled. However, they don't control any collapsible content so aria-expanded is not used.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense. Thanks for looking into it.

@gziolo gziolo force-pushed the add/sidebar-pinning branch from fef693b to 2c4b4c7 Compare May 9, 2018 05:22
- Required: No
- Default: `true`

##### icon
Copy link
Member

Choose a reason for hiding this comment

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

Should this be a property of registerPlugin rather than a prop of PluginSidebar ?

Copy link
Member Author

Choose a reason for hiding this comment

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

As I had a similar dilemma. I will explore how much work is required to move it to registerPlugin.

Copy link
Member

Choose a reason for hiding this comment

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

I mean, even just as a default, it seems like many / most plugins would have a single icon to represent themselves throughout the entire experience.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I like it. It should work. I’ll update PR on Monday 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

I added an option to provide an icon property as part of plugin's registration with 15a51cc. It should give it more flexibility because I left the capability to override this icon for the individual components.

if ( action.type === 'TOGGLE_PINNED_PLUGIN_ITEM' ) {
return {
...state,
[ action.pluginName ]: isUndefined( state[ action.pluginName ] ) ?
Copy link
Member

Choose a reason for hiding this comment

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

Should we want to use Object#hasOwnProperty here so we're not dealing with oddities with my aptly-named "constructor" plugin?

$ n_
n_ > var state = {};
undefined
n_ > var action = { pluginName: 'constructor' };
undefined
n_ > _.isUndefined( state[ action.pluginName ] );
false

const defaultValue = true;
const pinnedPluginItems = getPreference( state, 'pinnedPluginItems', {} );

return isUndefined( pinnedPluginItems[ pluginName ] ) ?
Copy link
Member

Choose a reason for hiding this comment

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

Maybe leverage get and it's defaultValue argument?

return get( getPreference( state, 'pinnedPluginItems', {} ), [ pluginName ], true );

https://lodash.com/docs/4.17.10#get


return isUndefined( pinnedPluginItems[ pluginName ] ) ?
defaultValue :
Boolean( pinnedPluginItems[ pluginName ] );
Copy link
Member

Choose a reason for hiding this comment

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

When are we expecting the value to be anything other than a boolean here to warrant the coercion?

Copy link
Member Author

@gziolo gziolo May 11, 2018

Choose a reason for hiding this comment

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

I don’t think so.

I also agree that get from lodash will be easier to read in both places. I forgot about the fact that it checks if value is defined 😎

@gziolo gziolo force-pushed the add/sidebar-pinning branch from 2c4b4c7 to 86cb16b Compare May 13, 2018 08:47
@gziolo
Copy link
Member Author

gziolo commented May 13, 2018

@aduth, I addressed your feedback, should be ready for another check.

@jasmussen
Copy link
Contributor

I think this would be good to get in sooner rather than later. 👍 👍

@gziolo gziolo force-pushed the add/sidebar-pinning branch from 86cb16b to 7a31494 Compare May 14, 2018 09:39
@gziolo gziolo removed the Needs Design Feedback Needs general design feedback. label May 14, 2018
@gziolo
Copy link
Member Author

gziolo commented May 14, 2018

Fixed a regression introduced in Document & Block sidebar with 7a31494.

Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

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

Works great 👍

import Sidebar from '../';
import SidebarHeader from '../sidebar-header';

/**
* Renders the plugin sidebar component.
*
* @param {Object} props Element props.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Blank comment line between @param and @return.

@@ -22,6 +22,10 @@
display: none;
margin-left: auto;

~ .components-icon-button {
margin-left: 0;
Copy link
Member

Choose a reason for hiding this comment

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

Do we really want no margin between the plugin buttons?

image

Copy link
Member Author

Choose a reason for hiding this comment

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

Added 4px margin after recommendation from @jasmussen:

screen shot 2018-05-14 at 16 09 49
screen shot 2018-05-14 at 16 10 23

* When the value is not set it defaults to true.
*
* @param {Object} state Global application state.
* @param {string} pluginName Plugin item name.
Copy link
Member

Choose a reason for hiding this comment

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

Similar nit: Documentation standards recommend @param and @return to be separate groups, no need to align between the two.

https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/#functions

@@ -23,6 +23,7 @@ const plugins = {};
* @param {string} name The name of the plugin.
* @param {Object} settings The settings for this plugin.
* @param {Function} settings.render The function that renders the plugin.
* @param {string} settings.icon An icon to be shown in the UI.
Copy link
Member

Choose a reason for hiding this comment

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

Can be element as well?

Includes also JSDoc updates.
@gziolo gziolo merged commit f071482 into master May 14, 2018
@gziolo gziolo deleted the add/sidebar-pinning branch May 14, 2018 14:31
@mtias
Copy link
Member

mtias commented May 18, 2018

This a great final touch for the Plugins API. Great work @gziolo !

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 [Type] Task Issues or PRs that have been broken down into an individual action to take
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants