Add Custom Fields and Advanced Panels to Options modal#10676
Add Custom Fields and Advanced Panels to Options modal#10676youknowriad merged 11 commits intomasterfrom
Conversation
|
@youknowriad @aduth @gziolo: This is a first pass at completing #10210. Would greatly appreciate your thoughts on the overall direction1 before I work on adding tests and documentation. 1 Spoiler: it's hacky as hell 🙂 |
Allows the user to enable or disable 'Advanced Panels' (AKA Meta Boxes) via the Options modal. - The title and ID of each Meta Box is stored in the `core/edit-post` Redux store. - The `MetaBoxTitles` HOC provides an easy way to list all of the registered Meta Boxes within Gutenberg. - The `MetaBoxesVisibility` and `MetaBoxVisibility` components respond to the existing `isEditorPanelEnabled` selector and show/hide Meta Boxes using `el.style.display = 'none'`;
f864dc0 to
1779ddc
Compare
Re-enable the Custom Fields Meta Box which is included in Core, but have the panel disabled by default.
|
I've added the ability to enable and disable Custom Fields (disabled by default) and given the PR a more detailed description. Once I have a 👍 on the general direction here I'll start on the remaining tasks which are outlined above. |
|
I think the general direction is very good. It isn't an easy task to get more control over Meta Boxes :) @youknowriad spent weeks polishing the way it works and integrates with Gutenberg. I will leave some comments on the areas where I feel needs to be well documented to make it easier to maintain in the future. My first impression is that it should be emphasized that <MetaBoxTitles
renderItem={ ( title, id ) => (
<EnablePanelOption label={ title } panelName={ `meta-box-${ id }` } />
) }
/>We use this pattern in other places like |
youknowriad
left a comment
There was a problem hiding this comment.
Left some comments, the general approach is good for me. My comments are mainly refactoring comments that can be done separately if needed.
lib/meta-box-partial-page.php
Outdated
| */ | ||
| $script = 'window._wpLoadGutenbergEditor.then( function() { | ||
| wp.data.dispatch( \'core/edit-post\' ).setActiveMetaBoxLocations( ' . wp_json_encode( $active_meta_box_locations ) . ' ); | ||
| wp.data.dispatch( \'core/edit-post\' ).setMetaBoxTitles( ' . wp_json_encode( $meta_box_titles ) . ' ); |
There was a problem hiding this comment.
The name feels a bit weird to me. Is this really setMetaBoxTitles or more something like setAvailableMetaBoxes?
Also, maybe more logical to invert these two dispatches, as we should initialize the available metaboxes first and then tell it which one is active?
There was a problem hiding this comment.
We probably could also consolidate into one action since you proposed to have only one reducer.
| <div className="edit-post-layout__metaboxes"> | ||
| <MetaBoxes location="advanced" /> | ||
| </div> | ||
| <MetaBoxesVisibility /> |
There was a problem hiding this comment.
Is this really useful as its own isolated component? Should this be moved inside the MetaBoxes component instead? Granted, it will force us to filter it per location.
| } | ||
|
|
||
| return state; | ||
| } |
There was a problem hiding this comment.
Can we do a small refactoring to all these meta box states? I think we should just unify under a unique reducer (or combineReducers). Something like:
const metaboxes = {
isSaving: true,
locations: {
side: {
isActive: true,
metaboxes: [ // this could also be keyed by id
{ id: 'yoast', label: 'Yoast' }
]
}
}
}It's not crucial though but I feel we'd gain in clarity by rethinking these states. Thoughts?
| return getActiveMetaBoxLocations( state ).includes( location ); | ||
| } | ||
|
|
||
| export function getMetaBoxTitles( state ) { |
There was a problem hiding this comment.
Needs a JSDoc (and doc generation)
| }; | ||
| } | ||
|
|
||
| export function setMetaBoxTitles( titles ) { |
There was a problem hiding this comment.
Need JSDoc and Doc generation
| import { Fragment } from '@wordpress/element'; | ||
| import { withSelect } from '@wordpress/data'; | ||
|
|
||
| function MetaBoxTitles( { titles, titleWrapper } ) { |
There was a problem hiding this comment.
Actually, I'm not certain what value this component brings, as it seems like we already have the selector as an abstraction to get the meta-boxes titles
|
I'm going to try to push this to the finish line according to my comments to land in 4.1. I hope you agree with those @noisysocks |
|
Actually, I think there's a conceptual issue in this PR because we don't update the "active metabox locations" when we show/hide meta boxes. I'm going to try to fix it though. |
|
I've updated the PR. We can't add the "custom fields" meta box yet because this would have the side effect of adding a new "save" request for all Gutenberg installs and we want to optimize for metabox-less experience. So we'd likely need to handle it as a special case in this modal (triggering this option or button would reload the page with the meta box enabled) I'd appreciate some manual testing with ACF for instance. I still need to add tests and polish deprecations. |
gziolo
left a comment
There was a problem hiding this comment.
We should audit all updated selectors and ensure all those which return arrays won't cause performance issues.
| return get( | ||
| state.metaBoxes.locations, | ||
| [ location ], | ||
| [] |
There was a problem hiding this comment.
The default value will have a different reference on every call.
We probably should have some a reusable const or helper which always returns the same instance of an empty array or object to avoid re-renders on every call.
| * | ||
| * @return {Array} List of meta boxes. | ||
| */ | ||
| export function getAllMetaBoxes( state ) { |
There was a problem hiding this comment.
Should we use memoization helper here?
There was a problem hiding this comment.
Memoization should be avoided if possible to avoid by shaping state such that we don't need to filter / map over it.
| */ | ||
| export function getActiveMetaBoxLocations( state ) { | ||
| return state.activeMetaBoxLocations; | ||
| return Object.keys( state.metaBoxes.locations ) |
| return ( | ||
| <Fragment> | ||
| { map( metaBoxes, ( { id } ) => ( | ||
| <MetaBoxVisibility id={ id } /> |
There was a problem hiding this comment.
Does this not demand a key?
Edit: Yes, there's a console error.
There was a problem hiding this comment.
It does, copy/paste :(
| return get( | ||
| state.metaBoxes.locations, | ||
| [ location ], | ||
| [] |
There was a problem hiding this comment.
This will return a new array each time (i.e. busting component purity). Previously I've used a top-of-scope constant variable.
| allMetaBoxes = allMetaBoxes.concat( metaBoxes ); | ||
| } ); | ||
|
|
||
| return allMetaBoxes; |
There was a problem hiding this comment.
Another new-array-each-time. Could state shape be optimized for direct return?
There was a problem hiding this comment.
No matter the shape of the reducer, it will impact one selector or the other.
|
Seems to be an issue on master as well, but ACF meta values aren't saving and a dirty prompt is shown when trying to reload. |
|
@aduth Funny because I was testing with an old version of ACF and it was working fine :) |
|
I'm going to consider it as an ACF bug that's out of scope of the current PR. It was also mentioned here #10660. |
| */ | ||
| export function isMetaBoxLocationActive( state, location ) { | ||
| return getActiveMetaBoxLocations( state ).includes( location ); | ||
| return getMetaBoxesPerLocation( state, location ).length !== 0; |
There was a problem hiding this comment.
getMetaBoxesPerLocation is not guaranteed to return an array, at which point this throws an error.
There was a problem hiding this comment.
Is it necessary to render this component at all if ! isVisible? i.e. an ifCondition.
There was a problem hiding this comment.
It's necessary to handle the visibility.
There was a problem hiding this comment.
Possible simplification?
return flatten( values( state.metaBoxes.locations ) );7aba1e3 to
673750e
Compare
|
Thanks for your help on this. @noisysocks Let's try to tackle #3228 as a follow-up using this approach or similar #10676 (comment) |
|
Thanks for taking over @youknowriad! I love the approach of handling everything in
👍 Will look into this. |
* First pass at adding 'Advanced Panels' to Options modal Allows the user to enable or disable 'Advanced Panels' (AKA Meta Boxes) via the Options modal.
What this does
Closes #10210 and closes #3228.
Firstly, this PR allows the user to enable or disable Advanced Panels (AKA Meta Boxes) via the Screen Options modal that was added in #10215.
Secondly, it adds Custom Fields to Gutenberg in the form of an Advanced Panel that is disabled by default.
How it works
core/edit-postRedux store.MetaBoxTitlesHOC provides an easy way to list all of the registered Meta Boxes within Gutenberg.MetaBoxesVisibilityandMetaBoxVisibilitycomponents respond to the existingisEditorPanelEnabledselector and show/hide Meta Boxes usingel.style.display = 'none';postcustom) is no longer filtered out bygutenberg_filter_meta_boxesHow to test
Remaining tasks
.edit-post-layout__metaboxescontainer from appearing when all Advanced Panels are disabled