Skip to content

Commit

Permalink
FSE: Compute the block state of different entities separately (#21368)
Browse files Browse the repository at this point in the history
** Add Controlled Inner Blocks State **
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 hierarchy 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.

** Add useBlockSync hook to sync block-editor changes **
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.

** Refactor Inner Blocks to use new blockSync hook **
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.

** Integrate reducers with controlled inner blocks state **

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

** Wait for inserted blocks to show up in e2e test helper **

** Add e2e tests for multi-entity dirty state **

** Add unit tests for use block sync hook **
  • Loading branch information
noahtallen committed May 14, 2020
1 parent 196d8ce commit 4d23115
Show file tree
Hide file tree
Showing 21 changed files with 1,600 additions and 804 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ Namespace: `core/block-editor`.

<!-- START TOKEN(Autogenerated selectors|../../../../packages/block-editor/src/store/selectors.js) -->

<a name="areInnerBlocksControlled" href="#areInnerBlocksControlled">#</a> **areInnerBlocksControlled**

Checks if a given block has controlled inner blocks.

_Parameters_

- _state_ `Object`: Global application state.
- _clientId_ `string`: The block to check.

_Returns_

- `boolean`: True if the block has controlled inner blocks.

<a name="canInsertBlockType" href="#canInsertBlockType">#</a> **canInsertBlockType**

Determines if the given block type is allowed to be inserted into the block list.
Expand Down Expand Up @@ -56,6 +69,16 @@ containing its `blockName`, `clientId`, and current `attributes` state. This
is not the block's registration settings, which must be retrieved from the
blocks module registration store.

getBlock recurses through its inner blocks until all its children blocks have
been retrieved. Note that getBlock will not return the child inner blocks of
an inner block controller. This is because an inner block controller syncs
itself with its own entity, and should therefore not be included with the
blocks of a different entity. For example, say you call `getBlocks( TP )` to
get the blocks of a template part. If another template part is a child of TP,
then the nested template part's child blocks will not be returned. This way,
the template block itself is considered part of the parent, but the children
are not.

_Parameters_

- _state_ `Object`: Editor state.
Expand Down Expand Up @@ -238,10 +261,14 @@ _Returns_
<a name="getBlocks" href="#getBlocks">#</a> **getBlocks**

Returns all block objects for the current post being edited as an array in
the order they appear in the post.
the order they appear in the post. Note that this will exclude child blocks
of nested inner block controllers.

Note: It's important to memoize this selector to avoid return a new instance
on each call
on each call. We use the block cache state for each top-level block of the
given clientID. This way, the selector only refreshes on changes to blocks
associated with the given entity, and does not refresh when changes are made
to blocks which are part of different inner block controllers.

_Parameters_

Expand Down Expand Up @@ -1219,6 +1246,15 @@ _Parameters_

- _clientId_ `string`: Block client ID.

<a name="setHasControlledInnerBlocks" href="#setHasControlledInnerBlocks">#</a> **setHasControlledInnerBlocks**

Returns an action object that sets whether the block has controlled innerblocks.

_Parameters_

- _clientId_ `string`: The block's clientId.
- _hasControlledInnerBlocks_ `boolean`: True if the block's inner blocks are controlled.

<a name="setNavigationMode" href="#setNavigationMode">#</a> **setNavigationMode**

Generators that triggers an action used to enable or disable the navigation mode.
Expand Down
42 changes: 23 additions & 19 deletions packages/block-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,67 @@

## Unreleased

### Breaking Changes

- The block control value for `InnerBlocks` has been changed from `__experimentalBlocks` to `value` and is now considered a stable API.

## 3.7.0 (2020-02-10)

### New Features

- Add new `__experimentalEditorSkeleton` component. This has been moved over from the `@wordpress/edit-post` package, where it was an internal component called `EditorRegions`. Its class names have thus been renamed from `edit-post-editor-regions` to `block-editor-editor-skeleton`.
- Add new `__experimentalEditorSkeleton` component. This has been moved over from the `@wordpress/edit-post` package, where it was an internal component called `EditorRegions`. Its class names have thus been renamed from `edit-post-editor-regions` to `block-editor-editor-skeleton`.

## 3.3.0 (2019-11-14)

### New Features

- Added a `label` prop to `URLInput`. This allows the label to be set without needing to wrap the `URLInput` in a `BaseControl`.
- Added a `label` prop to `URLInput`. This allows the label to be set without needing to wrap the `URLInput` in a `BaseControl`.

### Deprecation

- `dropZoneUIOnly` prop in `MediaPlaceholder` component has been deprecated in favor of `disableMediaButtons` prop.
- `dropZoneUIOnly` prop in `MediaPlaceholder` component has been deprecated in favor of `disableMediaButtons` prop.

## 3.0.0 (2019-08-05)

### New Features

- Added a new `allowedFormats` prop to `RichText` to fine tune allowed formats. Deprecated the `formattingControls` prop in favour of this. Also added a `withoutInteractiveFormatting` to specifically disable format types that would insert interactive elements, which can not be nested.
- Added a new `allowedFormats` prop to `RichText` to fine tune allowed formats. Deprecated the `formattingControls` prop in favour of this. Also added a `withoutInteractiveFormatting` to specifically disable format types that would insert interactive elements, which can not be nested.

### Breaking Changes

- `BlockEditorProvider` no longer renders a wrapping `SlotFillProvider` or `DropZoneProvider` (from `@wordpress/components`). For custom block editors, you should render your own as wrapping the `BlockEditorProvider`. A future release will include a new `BlockEditor` component for simple, standard usage. `BlockEditorProvider` will serve the simple purpose of establishing its own context for block editors.
- `BlockEditorProvider` no longer renders a wrapping `SlotFillProvider` or `DropZoneProvider` (from `@wordpress/components`). For custom block editors, you should render your own as wrapping the `BlockEditorProvider`. A future release will include a new `BlockEditor` component for simple, standard usage. `BlockEditorProvider` will serve the simple purpose of establishing its own context for block editors.

## 2.2.0 (2019-06-12)

### Internal

- Refactored `BlockSettingsMenu` to use `DropdownMenu` from `@wordpress/components`.
- Refactored `BlockSettingsMenu` to use `DropdownMenu` from `@wordpress/components`.

## 2.0.0 (2019-04-16)

### New Features

- Added the `addToGallery` property to the `MediaUpload` interface. The property allows users to open the media modal in the `gallery-library`instead of `gallery-edit` state.
- Added the `addToGallery` property to the `MediaPlaceholder` component. The component passes the property to the `MediaUpload` component used inside the placeholder.
- Added the `isAppender` property to the `MediaPlaceholder` component. The property changes the look of the placeholder to be adequate to scenarios where new files are added to an already existing set of files, e.g., adding files to a gallery.
- Added the `dropZoneUIOnly` property to the `MediaPlaceholder` component. The property makes the `MediaPlaceholder` only render a dropzone without any other additional UI.
- Added a cancel link to the list of buttons in the `MediaPlaceholder` component which appears if an `onCancel` handler exists.
- Added the usage of `mediaPreview` for the `Placeholder` component to the `MediaPlaceholder` component.
- Added a an `onDoubleClick` event handler to the `MediaPlaceholder` component.
- Added a way to pass special `ref` property to the `PlainText` component.
- The `URLPopover` component now passes through all unhandled props to the underlying Popover component.
- Added the `addToGallery` property to the `MediaUpload` interface. The property allows users to open the media modal in the `gallery-library`instead of `gallery-edit` state.
- Added the `addToGallery` property to the `MediaPlaceholder` component. The component passes the property to the `MediaUpload` component used inside the placeholder.
- Added the `isAppender` property to the `MediaPlaceholder` component. The property changes the look of the placeholder to be adequate to scenarios where new files are added to an already existing set of files, e.g., adding files to a gallery.
- Added the `dropZoneUIOnly` property to the `MediaPlaceholder` component. The property makes the `MediaPlaceholder` only render a dropzone without any other additional UI.
- Added a cancel link to the list of buttons in the `MediaPlaceholder` component which appears if an `onCancel` handler exists.
- Added the usage of `mediaPreview` for the `Placeholder` component to the `MediaPlaceholder` component.
- Added a an `onDoubleClick` event handler to the `MediaPlaceholder` component.
- Added a way to pass special `ref` property to the `PlainText` component.
- The `URLPopover` component now passes through all unhandled props to the underlying Popover component.

### Breaking Changes

- `CopyHandler` will now only catch cut/copy events coming from its `props.children`, instead of from anywhere in the `document`.
- `CopyHandler` will now only catch cut/copy events coming from its `props.children`, instead of from anywhere in the `document`.

### Internal

- Improved handling of blocks state references for unchanging states.
- Updated handling of blocks state to effectively ignored programmatically-received blocks data (e.g. reusable blocks received from editor).
- Improved handling of blocks state references for unchanging states.
- Updated handling of blocks state to effectively ignored programmatically-received blocks data (e.g. reusable blocks received from editor).

## 1.0.0 (2019-03-06)

### New Features

- Initial version.
- Initial version.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* External dependencies
*/
import { mapValues } from 'lodash';

/**
* Block context cache, implemented as a WeakMap mapping block types to a
* WeakMap mapping attributes object to context value.
*
* @type {WeakMap<string,WeakMap<string,*>>}
*/
const BLOCK_CONTEXT_CACHE = new WeakMap();

/**
* Returns a cached context object value for a given set of attributes for the
* block type.
*
* @param {Record<string,*>} attributes Block attributes object.
* @param {WPBlockType} blockType Block type settings.
*
* @return {Record<string,*>} Context value.
*/
export default function getBlockContext( attributes, blockType ) {
if ( ! BLOCK_CONTEXT_CACHE.has( blockType ) ) {
BLOCK_CONTEXT_CACHE.set( blockType, new WeakMap() );
}

const blockTypeCache = BLOCK_CONTEXT_CACHE.get( blockType );
if ( ! blockTypeCache.has( attributes ) ) {
const context = mapValues(
blockType.providesContext,
( attributeName ) => attributes[ attributeName ]
);

blockTypeCache.set( attributes, context );
}

return blockTypeCache.get( attributes );
}
Loading

0 comments on commit 4d23115

Please sign in to comment.