-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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 InnerBlocks locking #6991
Add InnerBlocks locking #6991
Conversation
editor/utils/block-list.js
Outdated
static getDerivedStateFromProps( props ) { | ||
const newSettings = { | ||
allowedBlocks: props.allowedBlocks, | ||
locking: getHighestLocking( props.locking, props.parentLocking ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we compare the locking
prop with the parent locking
prop? Shouldn't we just always use the prop passed to InnerBlocks
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @youknowriad, this is what allows us to propagate the locking to descendants. Even if a child block sets no locking at all or a less restrictive locking the locking of the parent applies. So for example, if the parent adds locking and uses columns block the locking applies also to columns. I'm not certain if this is the right behavior and in fact, I'm now considering changing to just propagate the locking if the child block did not set locking at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I'd consider props.locking || props.parentLocking
a better approach here :) (or something in that vein. We could also question whether propagation is something we want (I know we do this at the moment)
editor/store/selectors.js
Outdated
return false; | ||
} | ||
|
||
const parentBlockListSettings = getBlockListSettings( state, parentUID ); | ||
const parentAllowedBlocks = get( parentBlockListSettings, [ 'supportedBlocks' ] ); | ||
const parentAllowedBlocks = get( parentBlockListSettings, [ 'allowedBlocks' ] ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this like an unrelated fix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, it's renamed nvm
I'm glad to see this being worked on. I wonder if we should support setting this prop in the "template" as well? Like we currently do for the children blocks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm missing something but the documentation and usage of the getLocking
function seems very inconsistent. I'm not quite following it.
editor/store/selectors.js
Outdated
@@ -1744,3 +1744,21 @@ export function getSupportedBlocks( state, uid, globallyEnabledBlockTypes ) { | |||
export function getEditorSettings( state ) { | |||
return state.settings; | |||
} | |||
|
|||
/* | |||
* Returns the editor settings. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment seems inaccurate; maybe it was a copy/paste oversight?
This seems to just return the locking setting for this block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True I totally missed the update to the comment. Thank you for the catch.
editor/store/selectors.js
Outdated
* | ||
* @param {Object} state Editor state. | ||
* | ||
* @return {Object} The editor settings object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to return the locking setting, which is a string (with particular values). Looks like this needs updating.
editor/store/selectors.js
Outdated
/* | ||
* Returns the editor settings. | ||
* | ||
* @param {Object} state Editor state. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This takes two arguments but only one is listed.
editor/store/selectors.js
Outdated
* | ||
* @return {Object} The editor settings object | ||
*/ | ||
export function getLocking( state, rootUID ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this function being called using getLocking( rootUID )
a few times. Am I misunderstanding something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The state is automatically injected by the data module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(see the README here https://github.com/WordPress/gutenberg/tree/master/data)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, okay. That is really hard to grok from reading the code 😢
return { | ||
isLocked: !! templateLock, | ||
isLocked: !! getLocking( rootUID ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function signature for getLocking
looks like getLocking( state, rootUID )
… seems like this is wrong.
editor/store/selectors.js
Outdated
} | ||
const blockListSettings = getBlockListSettings( state, rootUID ); | ||
if ( ! blockListSettings ) { | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why return void
? Wouldn't it make sense to at least return null
? It seems more semantically valid...
Looks like the docs for |
15d5c50
to
4487774
Compare
editor/store/selectors.js
Outdated
* | ||
* @return {?string} Locking in the context of a given block. | ||
*/ | ||
export function getLocking( state, rootUID ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name feels a bit odd to me. What can be the locked states returned in the string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The locking states can be all, insert or undefined/none. Maybe getLockingState is better alternative?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, shoot, I meant to mention this in my review and knew I was forgetting something.
getLockingStatus
or getLockSetting
or something similar would be better. getLocking
feels incomplete to me as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be more clear, yes: getLockedState
. The other concern is that it seems slightly backwards to consider the locked state as the property when most blocks are going to be unlocked.
@@ -92,13 +92,13 @@ export default compose( | |||
); | |||
|
|||
return { | |||
templateLock: getEditorSettings().templateLock, | |||
locking: getLockedState( insertionPoint.rootUID ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prop locking
should also have its name updated in accordance with getLockedState
, or should be renamed entirely to something like isLocked
with isLocked: !! getLockedState( … )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isLocked
is much nicer, I vote for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming was updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- At the editor level, we name template locking
templateLock
- At the block level, we name template locking
lock
function InnerBlocks( { BlockList, layouts, allowedBlocks, template } ) { | ||
return <BlockList { ...{ layouts, allowedBlocks, template } } />; | ||
function InnerBlocks( { BlockList, layouts, allowedBlocks, locking, template } ) { | ||
return <BlockList { ...{ layouts, allowedBlocks, locking, template } } />; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comments on naming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the prop to "lock", in templates we use template_lock, so I think using "lock" here is consistent. Unfortunately using isLocked in this case is not possible because we are not setting a boolean value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the prop to "lock", in templates we use template_lock, so I think using "lock" here is consistent. Unfortunately using isLocked in this case is not possible because we are not setting a boolean value.
But in the context of InnerBlocks, isn't the locking still referring to the template?
editor/store/selectors.js
Outdated
* @return {?string} Locked state in the context of a given block. | ||
*/ | ||
export function getLockedState( state, rootUID ) { | ||
if ( ! rootUID ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make rootUID
an explicitly optional param so we can avoid the check for falsy values?
getLockedState( state, rootUID = false ) {
if ( rootUID === false ) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @mcsf,
I followed your suggestion with a small change:
export function getLockedState( state, rootUID = null ) {
if ( rootUID === null ) {
Sometimes we use other selectors to know the rootUID and they may return null. In that case, we should consider that no rootUID was passed. Using this approach we can avoid extra checks before calling this selector (making the code simpler).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to revert this change and use the false check because I noticed a regression. Some selectors may return an empty string if rootUID is not set e.g. getBlockRootUID( uid ) returns an empty string for top-level blocks.
The best/simplest way to check for undefined, null or empty string seems to be a false check.
editor/utils/block-list.js
Outdated
return { | ||
block: getBlock( uid ), | ||
blockListSettings: getBlockListSettings( uid ), | ||
parentLocking: getLockedState( getBlockRootUID( uid ) ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comments on naming.
editor/utils/block-list.js
Outdated
}; | ||
if ( ! isShallowEqual( props.blockListSettings, newSettings ) ) { | ||
props.updateNestedSettings( newSettings ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On usage of getDerivedStateFromProps
:
- Strictly speaking, it should return
null
instead ofundefined
. - Side effects are to be avoided, and that is exactly what this particular method is doing. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @mcsf,
You are right it looks like getDerivedStateFromProps should be a pure function without side effects.
I updated the code to use componentDidUpdate as going back to componentWillReceiveProps is not a long-term option. componentDidUpdate also does not seems like a perfect solution as we may have double render executions but it seems the best option we have and according to the react docs users don't see the intermediate dom state.
a281470
to
28477cf
Compare
28477cf
to
03123ee
Compare
Would it be reasonable to represent inner block locking by |
There are two types of locking "all" and "insert" and this syntax does not allow to represent both types. Besides that having allowedBlocks={ [] } does not in fact prevent from inserting other blocks inside, because according to our API allowedBlocks does not prevent children from being inserted. In fact I expect many blocks setting allowedBlocks={ [] } while having many child blocks that we can insert inside. |
aabb66b
to
8812b72
Compare
This PR was rebased and most of the feedback are addressed. It is ready for a new look :) |
6c2966b
to
a136dfe
Compare
@@ -43,15 +43,19 @@ class InnerBlocks extends Component { | |||
updateNestedSettings() { | |||
const { | |||
blockListSettings, | |||
allowedBlocks: nextAllowedBlocks, | |||
allowedBlocks, | |||
lock, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mentioned at #6991 (comment): If we call this templateLock
at the editor level, why do we not call it the same here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea was to try to move locking out of being a template concept. I wanted the lock to be a setting blocks can make use. E.g. have settings that enable or disable locking.
But yes given that now its main use case is templates I updated the name to be templateLock.
allowedBlocks: nextAllowedBlocks, | ||
allowedBlocks, | ||
lock, | ||
parentLock, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is parentLock
being passed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I missed the commit of parentLock retrieval during the rebase. It was corrected.
14a759e
to
9bbccb8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works well in testing
if ( ! rootUID ) { | ||
return state.settings.templateLock; | ||
} | ||
const blockListSettings = getBlockListSettings( state, rootUID ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There aren't any unit tests for this new behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unit tests were added.
} ); | ||
const newSettings = { | ||
allowedBlocks, | ||
templateLock: templateLock === undefined ? parentLock : templateLock, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be the responsibility of someone rendering a BlockList
to pass the parentLock
, or should that component be smart enough to inherit?
If a parent block has locking but the child does not, which one wins?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When rendering a BlockList we don't pass the parentLock. We pass the templateLock. parentLock is just used for the case where templateLock is undefined and in that case we store parentLock in templateLock. Besides that case we never use parentLock again.
If a parent block has locking but the child does not, which one wins?
If the locking of an InnerBlocks area is undefined we use the locking of the parent. In all other cases, we use the templateLock passed even if the locking of the child is more permissive than the locking of the father.
Before we used always the less permissive locking of the tree #6991 (comment) but it was changed.
9bbccb8
to
50ccd28
Compare
Dismissing because I think the points were already addressed. If there is anything I missed, feel free to comment.
Description
The feature is required by #6993.
This PR adds the possibility to set locking in InnerBlocks in a similar way we can now do with the template locking.
Usage:
Locking can be all, insert or not set.
Right now, with template locking if we have locking active and a template with a column block we cannot insert anything into the column so it seems locking applies to children.
We followed the same logic in this PR locking applies to descends even if the descends set a less restrictive locking or no locking at all. We may change this behavior but if changing it we should also change it for the CPT templates case.
How has this been tested?
The best way is to use the test blocks in https://gist.github.com/jorgefilipecosta/2bac096fe3be7f6ff0de7452292161cb, and verify everything works as expected. To simplest way to have the blocks is to paste all the code in the developer console.