From 45b53e9b4c2423e9a9e7b6bc2fb3a3389799cad2 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 12 Jun 2023 11:57:36 +0100 Subject: [PATCH 1/8] Move selector to become private --- docs/reference-guides/data/data-core.md | 12 ------------ packages/core-data/README.md | 12 ------------ packages/core-data/src/private-selectors.ts | 13 +++++++++++++ packages/core-data/src/selectors.ts | 12 ------------ 4 files changed, 13 insertions(+), 36 deletions(-) diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 8db3a26dd09772..a66c0991e3d274 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -329,18 +329,6 @@ _Returns_ - `any`: The entity record's save error. -### getNavigationFallbackId - -Retrieve the fallback Navigation. - -_Parameters_ - -- _state_ `State`: Data state. - -_Returns_ - -- `EntityRecordKey | undefined`: The ID for the fallback Navigation post. - ### getRawEntityRecord Returns the entity's record object by key, with its attributes mapped to their raw values. diff --git a/packages/core-data/README.md b/packages/core-data/README.md index ade2293efe167a..63e6e28db08d53 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -506,18 +506,6 @@ _Returns_ - `any`: The entity record's save error. -### getNavigationFallbackId - -Retrieve the fallback Navigation. - -_Parameters_ - -- _state_ `State`: Data state. - -_Returns_ - -- `EntityRecordKey | undefined`: The ID for the fallback Navigation post. - ### getRawEntityRecord Returns the entity's record object by key, with its attributes mapped to their raw values. diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index 0ac2c750239692..1e253b900e1cbb 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -4,6 +4,7 @@ import type { State, UndoEdit } from './selectors'; type Optional< T > = T | undefined; +type EntityRecordKey = string | number; /** * Returns the previous edit from the current undo offset @@ -28,3 +29,15 @@ export function getUndoEdits( state: State ): Optional< UndoEdit[] > { export function getRedoEdits( state: State ): Optional< UndoEdit[] > { return state.undo.list[ state.undo.list.length + state.undo.offset ]; } + +/** + * Retrieve the fallback Navigation. + * + * @param state Data state. + * @return The ID for the fallback Navigation post. + */ +export function getNavigationFallbackId( + state: State +): EntityRecordKey | undefined { + return state.navigationFallbackId; +} diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index a6b7774d37094c..142d24a9d2b8dc 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -1257,18 +1257,6 @@ export function getBlockPatternCategories( state: State ): Array< any > { return state.blockPatternCategories; } -/** - * Retrieve the fallback Navigation. - * - * @param state Data state. - * @return The ID for the fallback Navigation post. - */ -export function getNavigationFallbackId( - state: State -): EntityRecordKey | undefined { - return state.navigationFallbackId; -} - /** * Returns the revisions of the current global styles theme. * From 8a3cf4c60687cced2cf7b5572c93f73fd8d08f27 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 26 Jun 2023 17:57:19 +0300 Subject: [PATCH 2/8] adds basic lock functionality --- packages/block-library/src/navigation/edit/index.js | 3 +-- .../block-library/src/navigation/edit/lock-unlock.js | 10 ++++++++++ packages/core-data/src/index.js | 5 +++++ packages/core-data/src/lock-unlock.js | 10 ++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 packages/block-library/src/navigation/edit/lock-unlock.js create mode 100644 packages/core-data/src/lock-unlock.js diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 2fe86825bb6b11..7ec990d5b7389c 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -69,7 +69,6 @@ import ManageMenusButton from './manage-menus-button'; import MenuInspectorControls from './menu-inspector-controls'; import DeletedNavigationWarning from './deleted-navigation-warning'; import { unlock } from '../../lock-unlock'; - const { useBlockEditingMode } = unlock( blockEditorPrivateApis ); function Navigation( { @@ -224,7 +223,7 @@ function Navigation( { // that automatically saves the menu as an entity when changes are made to the inner blocks. const hasUnsavedBlocks = hasUncontrolledInnerBlocks && ! isEntityAvailable; - const { getNavigationFallbackId } = useSelect( coreStore ); + const { getNavigationFallbackId } = unlock( useSelect( coreStore ) ); const navigationFallbackId = ! ( ref || hasUnsavedBlocks ) ? getNavigationFallbackId() diff --git a/packages/block-library/src/navigation/edit/lock-unlock.js b/packages/block-library/src/navigation/edit/lock-unlock.js new file mode 100644 index 00000000000000..5fdca775a27b89 --- /dev/null +++ b/packages/block-library/src/navigation/edit/lock-unlock.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; + +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', + '@wordpress/data' + ); diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index c2b491fa8c1ea1..e6f2b50abccc55 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -13,6 +13,8 @@ import * as resolvers from './resolvers'; import createLocksActions from './locks/actions'; import { rootEntitiesConfig, getMethodName } from './entities'; import { STORE_NAME } from './name'; +import { unlock } from './lock-unlock'; +import { getNavigationFallbackId } from './private-selectors'; // The entity selectors/resolvers and actions are shortcuts to their generic equivalents // (getEntityRecord, getEntityRecords, updateEntityRecord, updateEntityRecords) @@ -63,6 +65,9 @@ const storeConfig = () => ( { */ export const store = createReduxStore( STORE_NAME, storeConfig() ); register( store ); +unlock( store ).registerPrivateSelectors( { + getNavigationFallbackId, +} ); export { default as EntityProvider } from './entity-provider'; export * from './entity-provider'; diff --git a/packages/core-data/src/lock-unlock.js b/packages/core-data/src/lock-unlock.js new file mode 100644 index 00000000000000..5fdca775a27b89 --- /dev/null +++ b/packages/core-data/src/lock-unlock.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; + +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', + '@wordpress/data' + ); From 77d7ffc1b2131b9c4624cfd8c3e72f4b302bfa65 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 26 Jun 2023 18:01:07 +0300 Subject: [PATCH 3/8] remove useless lock-unlock file --- .../block-library/src/navigation/edit/lock-unlock.js | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 packages/block-library/src/navigation/edit/lock-unlock.js diff --git a/packages/block-library/src/navigation/edit/lock-unlock.js b/packages/block-library/src/navigation/edit/lock-unlock.js deleted file mode 100644 index 5fdca775a27b89..00000000000000 --- a/packages/block-library/src/navigation/edit/lock-unlock.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * WordPress dependencies - */ -import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; - -export const { lock, unlock } = - __dangerousOptInToUnstableAPIsOnlyForCoreModules( - 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', - '@wordpress/data' - ); From 00844b5d5bf1b27b36ca6ce31e46574f8e2e9743 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Tue, 27 Jun 2023 21:01:54 +0300 Subject: [PATCH 4/8] map private selectors to resolvers --- packages/core-data/src/index.js | 2 +- packages/data/src/redux-store/index.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index e6f2b50abccc55..0298d3cdd846cf 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -64,10 +64,10 @@ const storeConfig = () => ( { * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore */ export const store = createReduxStore( STORE_NAME, storeConfig() ); -register( store ); unlock( store ).registerPrivateSelectors( { getNavigationFallbackId, } ); +register( store ); // Register store after unlocking private selectors to allow resolvers to use them. export { default as EntityProvider } from './entity-provider'; export * from './entity-provider'; diff --git a/packages/data/src/redux-store/index.js b/packages/data/src/redux-store/index.js index 933cd11f3ddba1..2088f87c1e8a59 100644 --- a/packages/data/src/redux-store/index.js +++ b/packages/data/src/redux-store/index.js @@ -145,7 +145,7 @@ function createBindingCache( bind ) { */ export default function createReduxStore( key, options ) { const privateActions = {}; - const privateSelectors = {}; + let privateSelectors = {}; const privateRegistrationFunctions = { privateActions, registerPrivateActions: ( actions ) => { @@ -259,6 +259,12 @@ export default function createReduxStore( key, options ) { store, resolversCache ); + privateSelectors = mapSelectorsWithResolvers( + privateSelectors, + resolvers, + store, + resolversCache + ); } const boundPrivateSelectors = createBindingCache( bindSelector ); From d64c73d6b20aa7cb8464bfda8bef4d4947a917fb Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 28 Jun 2023 08:57:19 +0100 Subject: [PATCH 5/8] Unlock the other usage --- .../sidebar-navigation-screen-navigation-menus/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js index 57ebfd5a2e9533..9881fc30149efe 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js @@ -21,6 +21,7 @@ import { PRELOADED_NAVIGATION_MENUS_QUERY } from './constants'; import { useLink } from '../routes/link'; import SingleNavigationMenu from '../sidebar-navigation-screen-navigation-menu/single-navigation-menu'; import useNavigationMenuHandlers from '../sidebar-navigation-screen-navigation-menu/use-navigation-menu-handlers'; +import { unlock } from '../../lock-unlock'; // Copied from packages/block-library/src/navigation/edit/navigation-menu-selector.js. function buildMenuLabel( title, id, status ) { @@ -55,9 +56,7 @@ export default function SidebarNavigationScreenNavigationMenus() { const isLoading = isResolvingNavigationMenus && ! hasResolvedNavigationMenus; - const getNavigationFallbackId = useSelect( - ( select ) => select( coreStore ).getNavigationFallbackId - ); + const { getNavigationFallbackId } = unlock( useSelect( coreStore ) ); const firstNavigationMenu = navigationMenus?.[ 0 ]; From 06ad9ce4f7b65cf3db69b4a618820dfa8de09231 Mon Sep 17 00:00:00 2001 From: scruffian Date: Wed, 28 Jun 2023 12:21:57 +0100 Subject: [PATCH 6/8] only create one fallback per session --- .../index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js index 9881fc30149efe..9d36e956cdca47 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/index.js @@ -42,6 +42,9 @@ function buildMenuLabel( title, id, status ) { ); } +// Save a boolean to prevent us creating a fallback more than once per session. +let hasCreatedFallback = false; + export default function SidebarNavigationScreenNavigationMenus() { const { records: navigationMenus, @@ -60,12 +63,18 @@ export default function SidebarNavigationScreenNavigationMenus() { const firstNavigationMenu = navigationMenus?.[ 0 ]; + // Save a boolean to prevent us creating a fallback more than once per session. + if ( firstNavigationMenu ) { + hasCreatedFallback = true; + } + // If there is no navigation menu found // then trigger fallback algorithm to create one. if ( ! firstNavigationMenu && ! isResolvingNavigationMenus && - hasResolvedNavigationMenus + hasResolvedNavigationMenus && + ! hasCreatedFallback ) { getNavigationFallbackId(); } From e990986b72dc4c4fe5bac8e2af399587a173669f Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 29 Jun 2023 12:21:31 +0200 Subject: [PATCH 7/8] Fix core-data duplicate private opt-in --- packages/core-data/src/index.js | 2 +- packages/core-data/src/lock-unlock.js | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 packages/core-data/src/lock-unlock.js diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 0298d3cdd846cf..98509d9f0383ba 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -13,7 +13,7 @@ import * as resolvers from './resolvers'; import createLocksActions from './locks/actions'; import { rootEntitiesConfig, getMethodName } from './entities'; import { STORE_NAME } from './name'; -import { unlock } from './lock-unlock'; +import { unlock } from './private-apis'; import { getNavigationFallbackId } from './private-selectors'; // The entity selectors/resolvers and actions are shortcuts to their generic equivalents diff --git a/packages/core-data/src/lock-unlock.js b/packages/core-data/src/lock-unlock.js deleted file mode 100644 index 5fdca775a27b89..00000000000000 --- a/packages/core-data/src/lock-unlock.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * WordPress dependencies - */ -import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; - -export const { lock, unlock } = - __dangerousOptInToUnstableAPIsOnlyForCoreModules( - 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', - '@wordpress/data' - ); From cea2b773f2d71eef48a9bac1b39aeedf75bf5554 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 29 Jun 2023 12:22:55 +0200 Subject: [PATCH 8/8] Data: bind resolvers to selectors individually, support private selectors --- packages/data/src/redux-store/index.js | 92 ++++++++++++-------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/packages/data/src/redux-store/index.js b/packages/data/src/redux-store/index.js index 2088f87c1e8a59..9b9067e60569b8 100644 --- a/packages/data/src/redux-store/index.js +++ b/packages/data/src/redux-store/index.js @@ -106,10 +106,10 @@ function createBindingCache( bind ) { const cache = new WeakMap(); return { - get( item ) { + get( item, itemName ) { let boundItem = cache.get( item ); if ( ! boundItem ) { - boundItem = bind( item ); + boundItem = bind( item, itemName ); cache.set( item, boundItem ); } return boundItem; @@ -145,7 +145,7 @@ function createBindingCache( bind ) { */ export default function createReduxStore( key, options ) { const privateActions = {}; - let privateSelectors = {}; + const privateSelectors = {}; const privateRegistrationFunctions = { privateActions, registerPrivateActions: ( actions ) => { @@ -212,7 +212,7 @@ export default function createReduxStore( key, options ) { get: ( target, prop ) => { const privateAction = privateActions[ prop ]; return privateAction - ? boundPrivateActions.get( privateAction ) + ? boundPrivateActions.get( privateAction, prop ) : actions[ prop ]; }, } ); @@ -224,7 +224,11 @@ export default function createReduxStore( key, options ) { lock( actions, allActions ); - function bindSelector( selector ) { + const resolvers = options.resolvers + ? mapResolvers( options.resolvers ) + : {}; + + function bindSelector( selector, selectorName ) { if ( selector.isRegistrySelector ) { selector.registry = registry; } @@ -232,8 +236,20 @@ export default function createReduxStore( key, options ) { const state = store.__unstableOriginalGetState(); return selector( state.root, ...args ); }; - boundSelector.hasResolver = false; - return boundSelector; + + const resolver = resolvers[ selectorName ]; + if ( ! resolver ) { + boundSelector.hasResolver = false; + return boundSelector; + } + + return mapSelectorWithResolver( + boundSelector, + selectorName, + resolver, + store, + resolversCache + ); } function bindMetadataSelector( selector ) { @@ -245,41 +261,26 @@ export default function createReduxStore( key, options ) { return boundSelector; } - let selectors = { + const selectors = { ...mapValues( metadataSelectors, bindMetadataSelector ), ...mapValues( options.selectors, bindSelector ), }; - let resolvers; - if ( options.resolvers ) { - resolvers = mapResolvers( options.resolvers ); - selectors = mapSelectorsWithResolvers( - selectors, - resolvers, - store, - resolversCache - ); - privateSelectors = mapSelectorsWithResolvers( - privateSelectors, - resolvers, - store, - resolversCache - ); - } - const boundPrivateSelectors = createBindingCache( bindSelector ); // Pre-bind the private selectors that have been registered by the time of // instantiation, so that registry selectors are bound to the registry. - for ( const privateSelector of Object.values( privateSelectors ) ) { - boundPrivateSelectors.get( privateSelector ); + for ( const [ selectorName, selector ] of Object.entries( + privateSelectors + ) ) { + boundPrivateSelectors.get( selector, selectorName ); } const allSelectors = new Proxy( () => {}, { get: ( target, prop ) => { const privateSelector = privateSelectors[ prop ]; return privateSelector - ? boundPrivateSelectors.get( privateSelector ) + ? boundPrivateSelectors.get( privateSelector, prop ) : selectors[ prop ]; }, } ); @@ -535,22 +536,24 @@ function mapResolvers( resolvers ) { } /** - * Returns resolvers with matched selectors for a given namespace. + * Returns a selector with a matched resolver. * Resolvers are side effects invoked once per argument set of a given selector call, * used in ensuring that the data needs for the selector are satisfied. * - * @param {Object} selectors The current selectors to be modified. - * @param {Object} resolvers Resolvers to register. + * @param {Object} selector The selector function to be bound. + * @param {string} selectorName The selector name. + * @param {Object} resolver Resolver to call. * @param {Object} store The redux store to which the resolvers should be mapped. * @param {Object} resolversCache Resolvers Cache. */ -function mapSelectorsWithResolvers( - selectors, - resolvers, +function mapSelectorWithResolver( + selector, + selectorName, + resolver, store, resolversCache ) { - function fulfillSelector( resolver, selectorName, args ) { + function fulfillSelector( args ) { const state = store.getState(); if ( @@ -596,17 +599,10 @@ function mapSelectorsWithResolvers( }, 0 ); } - return mapValues( selectors, ( selector, selectorName ) => { - const resolver = resolvers[ selectorName ]; - if ( ! resolver ) { - return selector; - } - - const selectorResolver = ( ...args ) => { - fulfillSelector( resolver, selectorName, args ); - return selector( ...args ); - }; - selectorResolver.hasResolver = true; - return selectorResolver; - } ); + const selectorResolver = ( ...args ) => { + fulfillSelector( args ); + return selector( ...args ); + }; + selectorResolver.hasResolver = true; + return selectorResolver; }