Skip to content
Permalink
Browse files
List View: render a fixed number of items (#35230)
* List View: render a fixed number of items
* List View: use tr placeholders instead of padding
  • Loading branch information
gwwar committed Oct 27, 2021
1 parent fb835b4 commit 45cffa2beaedd9146944f3a084b3748ce5572362
@@ -40,6 +40,7 @@ const config = require( '../config' );
* @property {number[]} inserterOpen Average time to open global inserter.
* @property {number[]} inserterSearch Average time to search the inserter.
* @property {number[]} inserterHover Average time to move mouse between two block item in the inserter.
* @property {number[]} listViewOpen Average time to open listView
*/

/**
@@ -52,7 +53,7 @@ const config = require( '../config' );
* @property {number=} firstContentfulPaint Represents the time when the browser first renders any text or media.
* @property {number=} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM.
* @property {number=} type Average type time.
* @property {number=} minType Minium type time.
* @property {number=} minType Minimum type time.
* @property {number=} maxType Maximum type time.
* @property {number=} focus Average block selection time.
* @property {number=} minFocus Min block selection time.
@@ -66,6 +67,9 @@ const config = require( '../config' );
* @property {number=} inserterHover Average time to move mouse between two block item in the inserter.
* @property {number=} minInserterHover Min time to move mouse between two block item in the inserter.
* @property {number=} maxInserterHover Max time to move mouse between two block item in the inserter.
* @property {number=} listViewOpen Average time to open list view.
* @property {number=} minListViewOpen Min time to open list view.
* @property {number=} maxListViewOpen Max time to open list view.
*/

/**
@@ -136,6 +140,9 @@ function curateResults( results ) {
inserterHover: average( results.inserterHover ),
minInserterHover: Math.min( ...results.inserterHover ),
maxInserterHover: Math.max( ...results.inserterHover ),
listViewOpen: average( results.listViewOpen ),
minListViewOpen: Math.min( ...results.listViewOpen ),
maxListViewOpen: Math.max( ...results.listViewOpen ),
};
}

@@ -378,6 +385,15 @@ async function runPerformanceTests( branches, options ) {
maxInserterHover: rawResults.map(
( r ) => r[ branch ].maxInserterHover
),
listViewOpen: rawResults.map(
( r ) => r[ branch ].listViewOpen
),
minListViewOpen: rawResults.map(
( r ) => r[ branch ].minListViewOpen
),
maxListViewOpen: rawResults.map(
( r ) => r[ branch ].maxListViewOpen
),
},
median
);
@@ -4,7 +4,8 @@

### Performance

- Avoid re-rendering all List View items on block focus [#35706](https://github.com/WordPress/gutenberg/pull/35706). These changes speed up block focus time in large posts by 80% when List View is open.
- Avoid re-rendering all List View items on block focus [#35706](https://github.com/WordPress/gutenberg/pull/35706). When List View is open Block focus time is 4 times faster in large posts.
- Render fixed number of items in List View [#35706](https://github.com/WordPress/gutenberg/pull/35230). Opening List View is 13 times faster in large posts.

### Breaking change

@@ -1,41 +1,106 @@
/**
* External dependencies
*/
import { map, compact } from 'lodash';
import { compact } from 'lodash';

/**
* WordPress dependencies
*/
import { Fragment } from '@wordpress/element';
import { Fragment, memo } from '@wordpress/element';

/**
* Internal dependencies
*/
import ListViewBlock from './block';
import { useListViewContext } from './context';

export default function ListViewBranch( props ) {
/**
* Given a block, returns the total number of blocks in that subtree. This is used to help determine
* the list position of a block.
*
* When a block is collapsed, we do not count their children as part of that total. In the current drag
* implementation dragged blocks and their children are not counted.
*
* @param {Object} block block tree
* @param {Object} expandedState state that notes which branches are collapsed
* @param {Array} draggedClientIds a list of dragged client ids
* @return {number} block count
*/
function countBlocks( block, expandedState, draggedClientIds ) {
const isDragged = draggedClientIds?.includes( block.clientId );
if ( isDragged ) {
return 0;
}
const isExpanded = expandedState[ block.clientId ] ?? true;
if ( isExpanded ) {
return (
1 +
block.innerBlocks.reduce(
countReducer( expandedState, draggedClientIds ),
0
)
);
}
return 1;
}
const countReducer = ( expandedState, draggedClientIds ) => (
count,
block
) => {
const isDragged = draggedClientIds?.includes( block.clientId );
if ( isDragged ) {
return count;
}
const isExpanded = expandedState[ block.clientId ] ?? true;
if ( isExpanded && block.innerBlocks.length > 0 ) {
return count + countBlocks( block, expandedState, draggedClientIds );
}
return count + 1;
};

function ListViewBranch( props ) {
const {
blocks,
selectBlock,
showBlockMovers,
showNestedBlocks,
level = 1,
path = '',
listPosition = 0,
fixedListWindow,
} = props;

const { expandedState, draggedClientIds } = useListViewContext();
const {
expandedState,
draggedClientIds,
__experimentalPersistentListViewFeatures,
} = useListViewContext();

const filteredBlocks = compact( blocks );
const blockCount = filteredBlocks.length;
let nextPosition = listPosition;

return (
<>
{ map( filteredBlocks, ( block, index ) => {
{ filteredBlocks.map( ( block, index ) => {
const { clientId, innerBlocks } = block;

if ( index > 0 ) {
nextPosition += countBlocks(
filteredBlocks[ index - 1 ],
expandedState,
draggedClientIds
);
}

const usesWindowing = __experimentalPersistentListViewFeatures;

const { itemInView } = fixedListWindow;

const blockInView =
! usesWindowing || itemInView( nextPosition );

const position = index + 1;
// This string value is used to trigger an animation change.
// This may be removed if we use a different animation library in the future.
const updatedPath =
path.length > 0
? `${ path }_${ position }`
@@ -49,20 +114,29 @@ export default function ListViewBranch( props ) {

const isDragged = !! draggedClientIds?.includes( clientId );

const showBlock = isDragged || blockInView;
return (
<Fragment key={ clientId }>
<ListViewBlock
block={ block }
selectBlock={ selectBlock }
isDragged={ isDragged }
level={ level }
position={ position }
rowCount={ blockCount }
siblingBlockCount={ blockCount }
showBlockMovers={ showBlockMovers }
path={ updatedPath }
isExpanded={ isExpanded }
/>
{ showBlock && (
<ListViewBlock
block={ block }
selectBlock={ selectBlock }
isDragged={ isDragged }
level={ level }
position={ position }
rowCount={ blockCount }
siblingBlockCount={ blockCount }
showBlockMovers={ showBlockMovers }
path={ updatedPath }
isExpanded={ isExpanded }
listPosition={ nextPosition }
/>
) }
{ ! showBlock && (
<tr>
<td className="block-editor-list-view-placeholder" />
</tr>
) }
{ hasNestedBlocks && isExpanded && ! isDragged && (
<ListViewBranch
blocks={ innerBlocks }
@@ -71,6 +145,8 @@ export default function ListViewBranch( props ) {
showNestedBlocks={ showNestedBlocks }
level={ level + 1 }
path={ updatedPath }
listPosition={ nextPosition + 1 }
fixedListWindow={ fixedListWindow }
/>
) }
</Fragment>
@@ -83,3 +159,5 @@ export default function ListViewBranch( props ) {
ListViewBranch.defaultProps = {
selectBlock: () => {},
};

export default memo( ListViewBranch );
@@ -1,10 +1,12 @@
/**
* WordPress dependencies
*/

import { useMergeRefs } from '@wordpress/compose';
import {
useMergeRefs,
__experimentalUseFixedWindowList as useFixedWindowList,
} from '@wordpress/compose';
import { __experimentalTreeGrid as TreeGrid } from '@wordpress/components';
import { AsyncModeProvider, useDispatch } from '@wordpress/data';
import { AsyncModeProvider, useDispatch, useSelect } from '@wordpress/data';
import {
useCallback,
useEffect,
@@ -67,6 +69,21 @@ function ListView(
) {
const { clientIdsTree, draggedClientIds } = useListViewClientIds( blocks );
const { selectBlock } = useDispatch( blockEditorStore );
const { visibleBlockCount } = useSelect(
( select ) => {
const { getGlobalBlockCount, getClientIdsOfDescendants } = select(
blockEditorStore
);
const draggedBlockCount =
draggedClientIds?.length > 0
? getClientIdsOfDescendants( draggedClientIds ).length + 1
: 0;
return {
visibleBlockCount: getGlobalBlockCount() - draggedBlockCount,
};
},
[ draggedClientIds ]
);
const selectEditorBlock = useCallback(
( clientId ) => {
selectBlock( clientId );
@@ -85,6 +102,18 @@ function ListView(
isMounted.current = true;
}, [] );

// List View renders a fixed number of items and relies on each having a fixed item height of 36px.
// If this value changes, we should also change the itemHeight value set in useFixedWindowList.
// See: https://github.com/WordPress/gutenberg/pull/35230 for additional context.
const [ fixedListWindow ] = useFixedWindowList(
elementRef,
36,
visibleBlockCount,
{
useWindowing: __experimentalPersistentListViewFeatures,
}
);

const expand = useCallback(
( clientId ) => {
if ( ! clientId ) {
@@ -158,6 +187,7 @@ function ListView(
selectBlock={ selectEditorBlock }
showNestedBlocks={ showNestedBlocks }
showBlockMovers={ showBlockMovers }
fixedListWindow={ fixedListWindow }
{ ...props }
/>
</ListViewContext.Provider>
@@ -62,6 +62,9 @@
display: none;
}

// List View renders a fixed number of items and relies on each item having a fixed height of 36px.
// If this value changes, we should also change the itemHeight value set in useFixedWindowList.
// See: https://github.com/WordPress/gutenberg/pull/35230 for additional context.
.block-editor-list-view-block-contents {
display: flex;
align-items: center;
@@ -357,3 +360,9 @@ $block-navigation-max-indent: 8;
box-shadow: none;
}

.block-editor-list-view-placeholder {
padding: 0;
margin: 0;
height: 36px;
}

Loading

0 comments on commit 45cffa2

Please sign in to comment.