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

Migrate block editor insert usage to preferences store #39632

Open
wants to merge 12 commits into
base: trunk
Choose a base branch
from
5 changes: 2 additions & 3 deletions packages/block-editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,11 @@ export const replaceBlocks =
type: 'REPLACE_BLOCKS',
clientIds,
blocks,
time: Date.now(),
indexToSelect,
initialPosition,
meta,
} );
dispatch.updateInsertUsage( blocks );
// To avoid a focus loss when removing the last block, assure there is
// always a default block if the last of the blocks have been removed.
dispatch.ensureDefaultBlock();
Expand Down Expand Up @@ -577,11 +577,11 @@ export const insertBlocks =
blocks: allowedBlocks,
index,
rootClientId,
time: Date.now(),
updateSelection,
initialPosition: updateSelection ? initialPosition : null,
meta,
} );
dispatch.updateInsertUsage( allowedBlocks );
}
};

Expand Down Expand Up @@ -1394,7 +1394,6 @@ export function replaceInnerBlocks(
blocks,
updateSelection,
initialPosition: updateSelection ? initialPosition : null,
time: Date.now(),
talldan marked this conversation as resolved.
Show resolved Hide resolved
};
}

Expand Down
4 changes: 0 additions & 4 deletions packages/block-editor/src/store/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
*/
import { __, _x } from '@wordpress/i18n';

export const PREFERENCES_DEFAULTS = {
insertUsage: {},
};

/**
* The default editor settings
*
Expand Down
7 changes: 2 additions & 5 deletions packages/block-editor/src/store/defaults.native.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
/**
* Internal dependencies
*/
import {
PREFERENCES_DEFAULTS,
SETTINGS_DEFAULTS as SETTINGS,
} from './defaults.js';
import { SETTINGS_DEFAULTS as SETTINGS } from './defaults.js';

const SETTINGS_DEFAULTS = {
...SETTINGS,
Expand All @@ -20,4 +17,4 @@ const SETTINGS_DEFAULTS = {
},
};

export { PREFERENCES_DEFAULTS, SETTINGS_DEFAULTS };
talldan marked this conversation as resolved.
Show resolved Hide resolved
export { SETTINGS_DEFAULTS };
20 changes: 2 additions & 18 deletions packages/block-editor/src/store/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { createReduxStore, registerStore } from '@wordpress/data';
import { createReduxStore, register } from '@wordpress/data';

/**
* Internal dependencies
Expand Down Expand Up @@ -32,24 +32,8 @@ export const storeConfig = {
*/
export const store = createReduxStore( STORE_NAME, {
...storeConfig,
persist: [ 'preferences' ],
} );

// We will be able to use the `register` function once we switch
// the "preferences" persistence to use the new preferences package.
const registeredStore = registerStore( STORE_NAME, {
...storeConfig,
persist: [ 'preferences' ],
} );
Copy link
Member

Choose a reason for hiding this comment

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

This is why I'm interested in finishing this PR 🙂 I'd like to remove all usages of the deprecated registerStore function and the persist plugin, and this is the last existing instance.

unlock( registeredStore ).registerPrivateActions( privateActions );
unlock( registeredStore ).registerPrivateSelectors( privateSelectors );

// TODO: Remove once we switch to the `register` function (see above).
//
// Until then, private functions also need to be attached to the original
// `store` descriptor in order to avoid unit tests failing, which could happen
// when tests create new registries in which they register stores.
//
// @see https://github.com/WordPress/gutenberg/pull/51145#discussion_r1239999590
unlock( store ).registerPrivateActions( privateActions );
unlock( store ).registerPrivateSelectors( privateSelectors );
register( store );
68 changes: 68 additions & 0 deletions packages/block-editor/src/store/private-actions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/**
* WordPress dependencies
*/
import { store as blocksStore } from '@wordpress/blocks';
import { Platform } from '@wordpress/element';
import { store as preferencesStore } from '@wordpress/preferences';

/**
* Internal dependencies
Expand Down Expand Up @@ -382,3 +384,69 @@ export const modifyContentLockBlock =
focusModeToRevert
);
};

/**
* Updates the inserter usage statistics in the preferences store.
*
* Note: this function is an internal and not intended to ever be made
* non-private.
*
* @param {Array} blocks The array of blocks that were inserted.
*/
export const updateInsertUsage =
( blocks ) =>
( { registry } ) => {
const previousInsertUsage =
registry.select( preferencesStore ).get( 'core', 'insertUsage' ) ??
{};

const time = Date.now();

let updatedInsertUsage = blocks.reduce( ( previousState, block ) => {
const { attributes, name: blockName } = block;
let id = blockName;
const variation = registry
.select( blocksStore )
.getActiveBlockVariation( blockName, attributes );

if ( variation?.name ) {
id += '/' + variation.name;
}

if ( blockName === 'core/block' ) {
id += '/' + attributes.ref;
}

const previousCount = previousState?.[ id ]?.count ?? 0;

return {
...previousState,
[ id ]: {
time,
count: previousCount + 1,
},
};
}, previousInsertUsage );

// Ensure the list of blocks doesn't grow above `limit` items.
// This is to ensure the preferences store data doesn't grow too big
// given it's persisted in the database.
const limit = 100;
const entries = Object.entries( updatedInsertUsage );
if ( entries.length > limit ) {
// Most recently inserted blocks first.
entries.sort( ( a, b ) => b[ 1 ].time - a[ 1 ].time );

// Slice an array of items that are the newest and convert them
// back into object form.
const entriesToKeep = entries.slice( 0, limit );
updatedInsertUsage = Object.fromEntries( entriesToKeep );
}

unlock(
registry.dispatch( preferencesStore )
).markNextChangeAsExpensive();
registry
.dispatch( preferencesStore )
.set( 'core', 'insertUsage', updatedInsertUsage );
};
19 changes: 19 additions & 0 deletions packages/block-editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { createSelector, createRegistrySelector } from '@wordpress/data';
import { store as preferencesStore } from '@wordpress/preferences';

/**
* Internal dependencies
Expand Down Expand Up @@ -31,6 +32,8 @@ import {

export { getBlockSettings } from './get-block-settings';

const EMPTY_OBJECT = {};

/**
* Returns true if the block interface is hidden, or false otherwise.
*
Expand Down Expand Up @@ -511,3 +514,19 @@ export function getTemporarilyEditingAsBlocks( state ) {
export function getTemporarilyEditingFocusModeToRevert( state ) {
return state.temporarilyEditingFocusModeRevert;
}

/**
* Return all insert usage stats.
*
* This is only exported since registry selectors need to be exported. It's marked
* as unstable so that it's not considered part of the public API.
*
* @return {Object<string,Object>} An object with an `id` key representing the type
* of block and an object value that contains
* block insertion statistics.
*/
export const getInsertUsage = createRegistrySelector(
( registrySelect ) => () =>
registrySelect( preferencesStore ).get( 'core', 'insertUsage' ) ??
EMPTY_OBJECT
);
59 changes: 3 additions & 56 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import fastDeepEqual from 'fast-deep-equal/es6';
* WordPress dependencies
*/
import { pipe } from '@wordpress/compose';
import { combineReducers, select } from '@wordpress/data';
import { store as blocksStore } from '@wordpress/blocks';
import { combineReducers } from '@wordpress/data';

/**
* Internal dependencies
*/
import { PREFERENCES_DEFAULTS, SETTINGS_DEFAULTS } from './defaults';
import { SETTINGS_DEFAULTS } from './defaults';
import { insertAt, moveTo } from './array';

const identity = ( x ) => x;
Expand Down Expand Up @@ -1676,58 +1676,6 @@ export function settings( state = SETTINGS_DEFAULTS, action ) {
return state;
}

/**
* Reducer returning the user preferences.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {string} Updated state.
*/
export function preferences( state = PREFERENCES_DEFAULTS, action ) {
switch ( action.type ) {
case 'INSERT_BLOCKS':
case 'REPLACE_BLOCKS': {
const nextInsertUsage = action.blocks.reduce(
( prevUsage, block ) => {
const { attributes, name: blockName } = block;
let id = blockName;
// If a block variation match is found change the name to be the same with the
// one that is used for block variations in the Inserter (`getItemFromVariation`).
const match = select( blocksStore ).getActiveBlockVariation(
blockName,
attributes
);
if ( match?.name ) {
id += '/' + match.name;
}
if ( blockName === 'core/block' ) {
id += '/' + attributes.ref;
}

return {
...prevUsage,
[ id ]: {
time: action.time,
count: prevUsage[ id ]
? prevUsage[ id ].count + 1
: 1,
},
};
},
state.insertUsage
);

return {
...state,
insertUsage: nextInsertUsage,
};
}
}

return state;
}

/**
* Reducer returning an object where each key is a block client ID, its value
* representing the settings for its nested blocks.
Expand Down Expand Up @@ -2083,7 +2031,6 @@ const combinedReducers = combineReducers( {
insertionPoint,
template,
settings,
preferences,
lastBlockAttributesChange,
lastFocus,
editorMode,
Expand Down
28 changes: 9 additions & 19 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { unlock } from '../lock-unlock';

import {
getContentLockingParent,
getInsertUsage,
getTemporarilyEditingAsBlocks,
getTemporarilyEditingFocusModeToRevert,
} from './private-selectors';
Expand Down Expand Up @@ -1819,20 +1820,6 @@ export function canLockBlockType( state, nameOrType ) {
return !! state.settings?.canLockBlocks;
}

/**
* Returns information about how recently and frequently a block has been inserted.
*
* @param {Object} state Global application state.
* @param {string} id A string which identifies the insert, e.g. 'core/block/12'
*
* @return {?{ time: number, count: number }} An object containing `time` which is when the last
* insert occurred as a UNIX epoch, and `count` which is
* the number of inserts that have occurred.
*/
function getInsertUsage( state, id ) {
return state.preferences.insertUsage?.[ id ] ?? null;
}

/**
* Returns whether we can show a block type in the inserter
*
Expand All @@ -1859,7 +1846,8 @@ const canIncludeBlockTypeInInserter = ( state, blockType, rootClientId ) => {
*/
const getItemFromVariation = ( state, item ) => ( variation ) => {
const variationId = `${ item.id }/${ variation.name }`;
const { time, count = 0 } = getInsertUsage( state, variationId ) || {};
const insertUsage = getInsertUsage();
const { time, count = 0 } = insertUsage?.[ variationId ] ?? {};
return {
...item,
id: variationId,
Expand Down Expand Up @@ -1934,7 +1922,8 @@ const buildBlockTypeItem =
).some( ( { name } ) => name === blockType.name );
}

const { time, count = 0 } = getInsertUsage( state, id ) || {};
const insertUsage = getInsertUsage();
const { time, count = 0 } = insertUsage?.[ id ] ?? {};
const blockItemBase = {
id,
name: blockType.name,
Expand Down Expand Up @@ -2003,7 +1992,8 @@ export const getInserterItems = createRegistrySelector( ( select ) =>
}
: symbol;
const id = `core/block/${ reusableBlock.id }`;
const { time, count = 0 } = getInsertUsage( state, id ) || {};
const insertUsage = getInsertUsage();
const { time, count = 0 } = insertUsage?.[ id ] ?? {};
const frecency = calculateFrecency( time, count );

return {
Expand Down Expand Up @@ -2147,7 +2137,7 @@ export const getInserterItems = createRegistrySelector( ( select ) =>
getBlockTypes(),
unlock( select( STORE_NAME ) ).getReusableBlocks(),
state.blocks.order,
state.preferences.insertUsage,
getInsertUsage(),
...getInsertBlockTypeDependants( state, rootClientId ),
]
)
Expand Down Expand Up @@ -2214,7 +2204,7 @@ export const getBlockTransformItems = createSelector(
},
( state, blocks, rootClientId ) => [
getBlockTypes(),
state.preferences.insertUsage,
getInsertUsage(),
...getInsertBlockTypeDependants( state, rootClientId ),
]
);
Expand Down
Loading
Loading