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 InnerBlocks locking #6991

Merged
merged 1 commit into from
Jul 2, 2018
Merged

Conversation

jorgefilipecosta
Copy link
Member

@jorgefilipecosta jorgefilipecosta commented May 28, 2018

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:

edit() {
	return el( 'div', { style: { outline: '1px solid gray', padding: 5 } },
		el( InnerBlocks, {
			locking: 'insert', // all, insert, or none
			template: [
				[ 'core/paragraph', {
					placeholder: 'Enter locking insert p1 content…',
				} ],
				[ 'core/paragraph', {
					placeholder: 'Enter locking insert p2 content…',
				} ],
			],
		} )
	);
}

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.

@jorgefilipecosta jorgefilipecosta added the [Type] Enhancement A suggestion for improvement. label May 28, 2018
@jorgefilipecosta jorgefilipecosta self-assigned this May 28, 2018
@jorgefilipecosta jorgefilipecosta requested review from noisysocks and a team May 29, 2018 10:52
static getDerivedStateFromProps( props ) {
const newSettings = {
allowedBlocks: props.allowedBlocks,
locking: getHighestLocking( props.locking, props.parentLocking ),
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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)

return false;
}

const parentBlockListSettings = getBlockListSettings( state, parentUID );
const parentAllowedBlocks = get( parentBlockListSettings, [ 'supportedBlocks' ] );
const parentAllowedBlocks = get( parentBlockListSettings, [ 'allowedBlocks' ] );
Copy link
Contributor

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?

Copy link
Contributor

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

@youknowriad
Copy link
Contributor

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.

Copy link
Member

@tofumatt tofumatt left a 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.

@@ -1744,3 +1744,21 @@ export function getSupportedBlocks( state, uid, globallyEnabledBlockTypes ) {
export function getEditorSettings( state ) {
return state.settings;
}

/*
* Returns the editor settings.
Copy link
Member

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.

Copy link
Member Author

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.

*
* @param {Object} state Editor state.
*
* @return {Object} The editor settings object
Copy link
Member

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.

/*
* Returns the editor settings.
*
* @param {Object} state Editor state.
Copy link
Member

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.

*
* @return {Object} The editor settings object
*/
export function getLocking( state, rootUID ) {
Copy link
Member

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?

Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member

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 ),
Copy link
Member

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.

}
const blockListSettings = getBlockListSettings( state, rootUID );
if ( ! blockListSettings ) {
return;
Copy link
Member

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...

@tofumatt
Copy link
Member

Looks like the docs for getLocking are still off, but I see now I didn't understand how the data module worked.

@jorgefilipecosta jorgefilipecosta force-pushed the add/inner-blocks-locking branch from 15d5c50 to 4487774 Compare May 29, 2018 11:29
*
* @return {?string} Locking in the context of a given block.
*/
export function getLocking( state, rootUID ) {
Copy link
Member

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?

Copy link
Member Author

@jorgefilipecosta jorgefilipecosta May 30, 2018

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?

Copy link
Member

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.

Copy link
Member

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 ),
Copy link
Contributor

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( … )

Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Naming was updated.

Copy link
Member

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 } } />;
Copy link
Contributor

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.

Copy link
Member Author

@jorgefilipecosta jorgefilipecosta Jun 1, 2018

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.

Copy link
Member

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?

* @return {?string} Locked state in the context of a given block.
*/
export function getLockedState( state, rootUID ) {
if ( ! rootUID ) {
Copy link
Contributor

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 ) {

Copy link
Member Author

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).

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 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.

return {
block: getBlock( uid ),
blockListSettings: getBlockListSettings( uid ),
parentLocking: getLockedState( getBlockRootUID( uid ) ),
Copy link
Contributor

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.

};
if ( ! isShallowEqual( props.blockListSettings, newSettings ) ) {
props.updateNestedSettings( newSettings );
}
Copy link
Contributor

Choose a reason for hiding this comment

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

On usage of getDerivedStateFromProps:

Copy link
Member Author

@jorgefilipecosta jorgefilipecosta Jun 1, 2018

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.

@aduth
Copy link
Member

aduth commented Jun 20, 2018

Would it be reasonable to represent inner block locking by allowedBlocks={ [] } ?

@jorgefilipecosta
Copy link
Member Author

Would it be reasonable to represent inner block locking by allowedBlocks={ [] } ?

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.

@jorgefilipecosta jorgefilipecosta force-pushed the add/inner-blocks-locking branch 2 times, most recently from aabb66b to 8812b72 Compare June 27, 2018 14:34
@jorgefilipecosta
Copy link
Member Author

This PR was rebased and most of the feedback are addressed. It is ready for a new look :)

@jorgefilipecosta jorgefilipecosta force-pushed the add/inner-blocks-locking branch 2 times, most recently from 6c2966b to a136dfe Compare June 30, 2018 11:47
@@ -43,15 +43,19 @@ class InnerBlocks extends Component {
updateNestedSettings() {
const {
blockListSettings,
allowedBlocks: nextAllowedBlocks,
allowedBlocks,
lock,
Copy link
Member

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?

Copy link
Member Author

@jorgefilipecosta jorgefilipecosta Jul 2, 2018

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,
Copy link
Member

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?

Copy link
Member Author

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.

@jorgefilipecosta jorgefilipecosta force-pushed the add/inner-blocks-locking branch 2 times, most recently from 14a759e to 9bbccb8 Compare July 2, 2018 11:15
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 well in testing

if ( ! rootUID ) {
return state.settings.templateLock;
}
const blockListSettings = getBlockListSettings( state, rootUID );
Copy link
Member

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.

Copy link
Member Author

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,
Copy link
Member

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?

Copy link
Member Author

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.

@jorgefilipecosta jorgefilipecosta force-pushed the add/inner-blocks-locking branch from 9bbccb8 to 50ccd28 Compare July 2, 2018 15:59
@jorgefilipecosta jorgefilipecosta dismissed tofumatt’s stale review July 2, 2018 17:00

Dismissing because I think the points were already addressed. If there is anything I missed, feel free to comment.

@jorgefilipecosta jorgefilipecosta merged commit 060fb5f into master Jul 2, 2018
@mtias mtias added this to the 3.2 milestone Jul 5, 2018
@youknowriad youknowriad deleted the add/inner-blocks-locking branch July 12, 2018 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants