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

FSE: Compute the block state of different entities separately #21368

Merged
merged 9 commits into from
May 14, 2020

Conversation

noahtallen
Copy link
Member

@noahtallen noahtallen commented Apr 2, 2020

Description

Ultimately, this PR fixes dirty and undo state in the site editor related to different entities. Edits to template parts, nested template parts, and the root template are now considered separately. To test this, make sure that changes you make to one entity do not cause any other entity to become dirty. Additionally, test that undoing a parent entity does not delete a child entity from the editor. Also test that you do not dirty the editor by switching between different templates/template parts from the template selector dropdown.

A few aspects of the block-editor were changed to accomplish these changes.

  1. This PR adds a bit of state which lets us keep track of which blocks are controlling their own inner blocks. This is important because a top-level entity, like a template in FSE, will often control all of the blocks in the editor. However, the template can render other entities, like a template part, which control and persist their own InnerBlocks. However, the template thinks that the template part's inner blocks are part of its own blocks, which becomes an issue when we look at the isDirty state. The template thinks that it is dirty even when only a template part has been updated.
  2. We refactored the sync mechanism for the BlockEditorProvider so that it can also be used for inner block controllers. This means that the same code is used any time an entity and block-editor state need to stay in sync.
  3. Various parts of the reducer state were fixed so that changes to one entity don't also affect others. For example, RESET_BLOCKS and REPLACE_INNER_BLOCKS were both deleting the blocks from other entities, which meant that those entities deleted their own content. Additionally, the cache state has been updated so that a parent block cache's are not updated past the controlling entity.
  4. We also updated the getBlock selector to not return controlled inner blocks. This way, blocks which belong to a child entity are not returned when asking for the blocks of a parent entity. This also meant updating the getBlocks cache so that it does not refresh when blocks belonging to a different entity are changed.
  5. We refactored InnerBlocks and BlockEditorProvider into functional components with hooks since we were already touching the code and needed to be able to use our new hook. This allows for more reusability between inner blocks and block editor provider, and even between inner blocks web and mobile component variants.

Many edge cases have been fixed so far in this PR, so it should be ready to go after we have more tests! Here is a list of a few things we were considering at the start of the PR that have now been fixed:

  • Not sure if nested controlled inner blocks work properly. My guess is that they do not work as expected. ✅ Nested inner block controllers should now work properly.
  • Will things like the undo stack still work correctly? ✅ Fixed
  • Need to probably update react native files ✅ Done
  • Need to add some test cases so that entity dirty state behavior doesn't break in the future. ✅ Complete

How has this been tested?

Locally in edit-site, in the post/page editor, and via unit/e2e tests.

Types of changes

Bug fix/enhancement

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR.

@github-actions
Copy link

github-actions bot commented Apr 2, 2020

Size Change: +53 B (0%)

Total Size: 827 kB

Filename Size Change
build/block-editor/index.js 104 kB +55 B (0%)
build/block-library/index.js 115 kB -4 B (0%)
build/edit-navigation/index.js 4.42 kB +1 B
build/edit-post/index.js 28 kB +1 B
build/edit-widgets/index.js 8.37 kB +1 B
build/editor/index.js 44.3 kB -1 B
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.02 kB 0 B
build/annotations/index.js 3.62 kB 0 B
build/api-fetch/index.js 4.08 kB 0 B
build/autop/index.js 2.82 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/index.js 6.63 kB 0 B
build/block-directory/style-rtl.css 764 B 0 B
build/block-directory/style.css 764 B 0 B
build/block-editor/style-rtl.css 10.6 kB 0 B
build/block-editor/style.css 10.6 kB 0 B
build/block-library/editor-rtl.css 7.12 kB 0 B
build/block-library/editor.css 7.12 kB 0 B
build/block-library/style-rtl.css 7.38 kB 0 B
build/block-library/style.css 7.38 kB 0 B
build/block-library/theme-rtl.css 683 B 0 B
build/block-library/theme.css 685 B 0 B
build/block-serialization-default-parser/index.js 1.88 kB 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/blocks/index.js 48.1 kB 0 B
build/components/index.js 181 kB 0 B
build/components/style-rtl.css 17 kB 0 B
build/components/style.css 16.9 kB 0 B
build/compose/index.js 6.66 kB 0 B
build/core-data/index.js 11.4 kB 0 B
build/data-controls/index.js 1.29 kB 0 B
build/data/index.js 8.43 kB 0 B
build/date/index.js 5.47 kB 0 B
build/deprecated/index.js 772 B 0 B
build/dom-ready/index.js 568 B 0 B
build/dom/index.js 3.1 kB 0 B
build/edit-navigation/style-rtl.css 618 B 0 B
build/edit-navigation/style.css 617 B 0 B
build/edit-post/style-rtl.css 12.2 kB 0 B
build/edit-post/style.css 12.2 kB 0 B
build/edit-site/index.js 12.1 kB 0 B
build/edit-site/style-rtl.css 5.22 kB 0 B
build/edit-site/style.css 5.22 kB 0 B
build/edit-widgets/style-rtl.css 4.69 kB 0 B
build/edit-widgets/style.css 4.69 kB 0 B
build/editor/editor-styles-rtl.css 425 B 0 B
build/editor/editor-styles.css 428 B 0 B
build/editor/style-rtl.css 5.07 kB 0 B
build/editor/style.css 5.08 kB 0 B
build/element/index.js 4.65 kB 0 B
build/escape-html/index.js 734 B 0 B
build/format-library/index.js 7.63 kB 0 B
build/format-library/style-rtl.css 502 B 0 B
build/format-library/style.css 502 B 0 B
build/hooks/index.js 2.14 kB 0 B
build/html-entities/index.js 622 B 0 B
build/i18n/index.js 3.56 kB 0 B
build/is-shallow-equal/index.js 710 B 0 B
build/keyboard-shortcuts/index.js 2.51 kB 0 B
build/keycodes/index.js 1.94 kB 0 B
build/list-reusable-blocks/index.js 3.12 kB 0 B
build/list-reusable-blocks/style-rtl.css 226 B 0 B
build/list-reusable-blocks/style.css 226 B 0 B
build/media-utils/index.js 5.29 kB 0 B
build/notices/index.js 1.79 kB 0 B
build/nux/index.js 3.4 kB 0 B
build/nux/style-rtl.css 616 B 0 B
build/nux/style.css 613 B 0 B
build/plugins/index.js 2.56 kB 0 B
build/primitives/index.js 1.5 kB 0 B
build/priority-queue/index.js 789 B 0 B
build/redux-routine/index.js 2.85 kB 0 B
build/rich-text/index.js 14.8 kB 0 B
build/server-side-render/index.js 2.68 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/token-list/index.js 1.28 kB 0 B
build/url/index.js 4.02 kB 0 B
build/viewport/index.js 1.84 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.18 kB 0 B

compressed-size-action

@mtias mtias added the [Feature] Nested / Inner Blocks Anything related to the experience of nested/inner blocks inside a larger container, like Group or P label Apr 3, 2020
@mtias
Copy link
Member

mtias commented Apr 3, 2020

I am not entirely sure "controlled inner blocks" is the right abstraction for the motivation here. What other cases are to be considered here apart from template parts? Would reusable blocks be the same? How does "no knowledge over inner blocks of template parts" interact with:

  • Undo / Redo stack.
  • Blocks attempting to read the presence of blocks in the page across template parts boundaries. Should it be possible?
  • Global state like block count, word count, etc.
  • Traversing outline structure (getting information about heading levels across template part boundaries).

It might also be that conceptualizing it as a "provider" can lead to better nomenclature.

@mtias mtias added the Needs Technical Feedback Needs testing from a developer perspective. label Apr 3, 2020
@noahtallen
Copy link
Member Author

That’s excellent feedback! I think there are two different things going on here, so we discussed adding two separate “getBlocks” cases to handle them. Essentially, there is:

  1. block-editor, which needs to be aware of all of the blocks it is rendering for all the reasons you listed above.
  2. A parent entity, which doesn’t need to be aware of the blocks a child entity contains.

Currently, the list of blocks is the same in both cases. Part of this is on the edit-site level, for example, where the onChange and value props of the block editor provider are 1:1 with the same methods for the template entity. So I think the ultimate goal for this PR is to make sure that the parent template entity does not include the controlled inner blocks of a template part when it updates. On approach is making the onChange handler not include those controlled blocks. So the approach using state gives us an opportunity to detect which blocks are being controlled, so that they could be excluded in that scenario. I imagine there is more than one way to accomplish it though!

@noahtallen noahtallen force-pushed the try/controlled-inner-blocks-detection branch from 3a1be24 to ed7cbc4 Compare April 3, 2020 22:27
@noahtallen
Copy link
Member Author

What other cases are to be considered here apart from template parts

This is a great thought, though. Indeed, I can imagine some more scenarios that could work similarly to this one. For example, when a local block has set a direct templateLock on its own InnerBlocks, it does its own template validation separately from the global template validation. (see #11681 for more context). I think a very similar mechanism could be used to resolve that issue, so could our abstraction be clearer to allow more than just "controlledInnerBlocks"? Maybe we ought to be tracking InnerBlocks metadata, or something along those lines.

When it comes to reusable blocks, I'm not familiar with how those work. But, with some testing locally, I see that they do not have the same issue we have with template parts. Editing a reusable block does not dirty the state of the entity which contains it. I'll need to figure out how reusable blocks manages to avoid the same problem, and maybe we can learn some lessons from it.

I wonder, since reusable blocks and template parts are so similar, if our UI and code around them should also work similarly

@noahtallen
Copy link
Member Author

Looking into reusable blocks a bit, and I notice that they are implemented by:

  1. Not including the reusable block sub-blocks in the main block editor provider. (which definitely affects the items mentioned above, like block count)
  2. Rendering its own blocks in the editor as a little BlockEditorProvider with only the reusable blocks.

Do you think that we would move to a similar mechanism as the template part block and do away with the nested BlockEditorProvider?

@chrisvanpatten
Copy link
Member

This came up a bit in today's Slack meeting in the context of reusable blocks and nested editors: https://wordpress.slack.com/archives/C02QB2JS7/p1586357559128600 (registration required)

At @MeredithCorp we're doing a lot of exploration around persisting data from nested block areas to other posts, post meta, and other "non-standard" data stores. The nested block editor approach works for this but comes with a number of limitations detailed in #19436.

@youknowriad suggested that working toward this approach might be a better longer term plan, so I'm very excited to see progress here. I haven't been following FSE too closely (it will likely never be an option for our brands) but I'll have to keep a closer eye on those issues as it seems like there will be more general benefits that come out of that work!

@noahtallen noahtallen force-pushed the try/controlled-inner-blocks-detection branch 2 times, most recently from a07b9c5 to 5ede108 Compare April 21, 2020 21:39
@noahtallen
Copy link
Member Author

I added much of the code from #19741 and integrated it with the inner block controller state (previously it did some detection of the block save property). Still not working, but it should eventually help solve the problem where onChange fires even when the changes happen in a controlled inner block area.

@youknowriad
Copy link
Contributor

@epiqueras you might also be interested in this PR :)

@epiqueras
Copy link
Contributor

Yeah, I suggested that we integrate #19741 into this, yesterday.

The issues discussed here happen because we clear the cache after resets, so we need to persist it. And, because edits bubble up to the root, when they should stop at the first parent without a save method, which we can easily do as shown in that PR.

@epiqueras
Copy link
Contributor

@noahtallen Let me know when this is ready for a review :)

@noahtallen noahtallen force-pushed the try/controlled-inner-blocks-detection branch from f4e8749 to ed30a91 Compare April 22, 2020 23:46
@noahtallen
Copy link
Member Author

The current status on this is: almost ready, but still buggy. Specifically, some actions trigger the persistence state correctly, but some don’t, and there are some cases where onChange even with the correct state. So I’m still working those issues out. I more or less understand what causes the issues independently, but it’s proving difficult to fix each bug without unfixing a different one! :) my goal is to have this ready for review tomorrow. (Thursday)

Allows us to tell if a certain block is controlling
its own InnerBlocks. This is helpful for entities
like template parts which persist their blocks
outside of the root block-editor onChange method.

Without this state, it would be hard to tell which
blocks belong to which entity. With this state, we
can easily traverse up the block heierarchy to find
the closest parent block which is an inner block
controller.

Practically, we can use this to make sure that the
dirty state between different entities is consistent.

This commit also updates the tests which refer to
the block state so that they all contain the new
state slice in the before state. Otherwise, some of
those tests were failing some some of the before
state did not exist.
The useBlockSync hook syncs a component't block-edior changes
with an onChange or onInput handler. The onChange handler is
used for persistent changes, and onInput is used for other
changes. All changes are scoped to the entity provided: e.g.
w.r.t. a root clientId. No changes are included which are
part of other entities. For example, a wp_template containing
a wp_template_part will not be notified of changes within
that template part, but only to changes in the template itself.

In other words, it ignores changes which happen in nested inner
block controllers.

Previously, very similar logic was used for BlockEditorProvider,
but it proved necessary to extract so that it could be integrated
into InnerBlocks later on. At the same time, we have refactored
BlockEditorProvider into a functional component with hooks to
save space and to remove needless higher order components.
This changes InnerBlocks into a functional component. Practically,
this should work exactly the same as before.

This allows us to more easily use the useBlockSync hook in order
to set up inner block controllers (i.e. with template parts). We
now specify a ControlledInnerBlocks component which handles block
sync instead of having that logic within InnerBlocks itself.

It also allows us to reuse the same logic as the root BlockEditorProvider,
so that we don't miss any of the logic and race condition fixes
that already exist there. I did try adding similar logic to the
old InnerBlocks component with what existed, but it proved very
prone to breakage and was hard to make compatible with the root
entity controller. It is much more straightforward to keep the
logic in the same place!

We also refactored the react native file for inner blocks, and
added forwardedRef and block context.
- Correctly include and exclude clientIDs from
  block reset depending on whether they are
  part of an inner block controller or not.
- Also handle inner block controllers themselves,
  not updating their cache or order when the
  action occurs
- Stop invalidating grandparent controller cache
- Fix undefined index access in selector state
- Handle replaceInnerBlocks action similar to
  the reset blocks action so that we do not
  inadvertantly delete a template part from the editor.

There were a lot of bugs around RESET_BLOCKS and
REPLACE_INNER_BLOCKS actions as relating to controlled
inner blocks. This is because those actions tend to delet
all blocks from the editor without providing back all of
the blocks to reinsert. This is because the entity which
dispatches the action does not have access to the inner
blocks of other entities, so they are not dispatched in
the action.

These changes update the reducers to make sure that nested
inner block controllers are not deleted when they should
stay in the editor.

Additionally, there are some fixes for the cache state so
that it does not invalidate when the changes affect a different
entity.
Some tests began failing since the block did not
exist immediately when the tests tried to interact
with the inserted blocks.
@noahtallen noahtallen force-pushed the try/controlled-inner-blocks-detection branch from a0d3968 to 7c31ce3 Compare May 12, 2020 19:02
@noahtallen
Copy link
Member Author

not sure what happened since the tests were passing earlier, but every e2e test is failing now. it almost seems like an env issue since the failures are related to not being able to activate the test plugins

@epiqueras
Copy link
Contributor

Yes, all other PRs are failing too.

Co-authored-by: Chris Van Patten <hello@chrisvanpatten.com>
Copy link
Member

@chrisvanpatten chrisvanpatten left a comment

Choose a reason for hiding this comment

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

Very excited for this all to land!

@noahtallen
Copy link
Member Author

cc @epiqueras @youknowriad do we think this is ready to go? 👀 Tests are green ✅

@noahtallen noahtallen changed the title FSE: Compute the dirty state of different entities separately FSE: Compute the block state of different entities separately May 14, 2020
@noahtallen noahtallen merged commit 4d23115 into master May 14, 2020
@noahtallen noahtallen deleted the try/controlled-inner-blocks-detection branch May 14, 2020 17:39
@github-actions github-actions bot added this to the Gutenberg 8.2 milestone May 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Nested / Inner Blocks Anything related to the experience of nested/inner blocks inside a larger container, like Group or P [Type] New API New API to be used by plugin developers or package users.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants