diff --git a/packages/edit-navigation/src/store/actions.js b/packages/edit-navigation/src/store/actions.js index 1f4cf6b7b530a1..a84629940d27fd 100644 --- a/packages/edit-navigation/src/store/actions.js +++ b/packages/edit-navigation/src/store/actions.js @@ -13,19 +13,25 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { + getNavigationPost, getPendingActions, isProcessingPost, + getMenuItemToClientIdMapping, resolveMenuItems, dispatch, apiFetch, } from './controls'; -import { menuItemsQuery, KIND, POST_TYPE } from './utils'; +import { menuItemsQuery } from './utils'; // Hits POST /wp/v2/menu-items once for every Link block that doesn't have an // associated menu item. (IDK what a good name for this is.) export const createMissingMenuItems = serializeProcessing( function* ( post ) { const menuId = post.meta.menuId; - const mapping = post.meta.menuItemIdToClientId; + + const mapping = yield { + type: 'GET_MENU_ITEM_TO_CLIENT_ID_MAPPING', + postId: post.id, + }; const clientIdToMenuId = invert( mapping ); const stack = [ post.blocks[ 0 ] ]; @@ -57,23 +63,29 @@ export const createMissingMenuItems = serializeProcessing( function* ( post ) { stack.push( ...block.innerBlocks ); } - yield dispatch( 'core', 'editEntityRecord', KIND, POST_TYPE, post.id, { - meta: { - ...post.meta, - menuItemIdToClientId: mapping, - }, - } ); + yield { + type: 'SET_MENU_ITEM_TO_CLIENT_ID_MAPPING', + postId: post.id, + mapping, + }; } ); export const saveNavigationPost = serializeProcessing( function* ( post ) { const menuId = post.meta.menuId; const menuItemsByClientId = mapMenuItemsByClientId( yield resolveMenuItems( menuId ), - post.meta.menuItemIdToClientId + yield getMenuItemToClientIdMapping( post.id ) ); try { - yield* batchSave( menuId, menuItemsByClientId, post.blocks[ 0 ] ); + const response = yield* batchSave( + menuId, + menuItemsByClientId, + post.blocks[ 0 ] + ); + if ( ! response.success ) { + throw new Error(); + } yield dispatch( 'core/notices', 'createSuccessNotice', @@ -131,7 +143,7 @@ function* batchSave( menuId, menuItemsByClientId, navigationBlock ) { ) ); - yield apiFetch( { + return yield apiFetch( { url: '/wp-admin/admin-ajax.php', method: 'POST', body, @@ -193,12 +205,13 @@ function computeCustomizedAttribute( blocks, menuId, menuItemsByClientId ) { function serializeProcessing( callback ) { return function* ( post ) { - const isProcessing = yield isProcessingPost( post.id ); + const postId = post.id; + const isProcessing = yield isProcessingPost( postId ); if ( isProcessing ) { yield { type: 'ENQUEUE_AFTER_PROCESSING', - id: post.id, + postId, action: callback, }; return { status: 'pending' }; @@ -206,7 +219,7 @@ function serializeProcessing( callback ) { yield { type: 'START_PROCESSING_POST', - id: post.id, + postId, }; try { @@ -214,16 +227,20 @@ function serializeProcessing( callback ) { } finally { yield { type: 'FINISH_PROCESSING_POST', - id: post.id, + postId, action: callback, }; - const pendingActions = yield getPendingActions( post.id ); + const pendingActions = yield getPendingActions( postId ); if ( pendingActions.length ) { const serializedCallback = serializeProcessing( pendingActions[ 0 ] ); - yield* serializedCallback( post ); + + // re-fetch the post as running the callback() likely updated it + yield* serializedCallback( + yield getNavigationPost( post.meta.menuId ) + ); } } }; diff --git a/packages/edit-navigation/src/store/controls.js b/packages/edit-navigation/src/store/controls.js index 7ef43da23358c2..ff5e140559b113 100644 --- a/packages/edit-navigation/src/store/controls.js +++ b/packages/edit-navigation/src/store/controls.js @@ -25,14 +25,21 @@ export function apiFetch( request ) { export function getPendingActions( postId ) { return { type: 'GET_PENDING_ACTIONS', - id: postId, + postId, }; } export function isProcessingPost( postId ) { return { type: 'IS_PROCESSING_POST', - id: postId, + postId, + }; +} + +export function getMenuItemToClientIdMapping( postId ) { + return { + type: 'GET_MENU_ITEM_TO_CLIENT_ID_MAPPING', + postId, }; } @@ -109,19 +116,26 @@ const controls = { } ), - GET_PENDING_ACTIONS: createRegistryControl( ( registry ) => ( { id } ) => { - const state = registry.stores[ - 'core/edit-navigation' - ].store.getState(); - return state.processingQueue[ id ]?.pendingActions || []; - } ), + GET_PENDING_ACTIONS: createRegistryControl( + ( registry ) => ( { postId } ) => { + return ( + getState( registry ).processingQueue[ postId ] + ?.pendingActions || [] + ); + } + ), + + IS_PROCESSING_POST: createRegistryControl( + ( registry ) => ( { postId } ) => { + return getState( registry ).processingQueue[ postId ]?.inProgress; + } + ), - IS_PROCESSING_POST: createRegistryControl( ( registry ) => ( { id } ) => { - const state = registry.stores[ - 'core/edit-navigation' - ].store.getState(); - return state.processingQueue[ id ]?.inProgress; - } ), + GET_MENU_ITEM_TO_CLIENT_ID_MAPPING: createRegistryControl( + ( registry ) => ( { postId } ) => { + return getState( registry ).mapping[ postId ] || {}; + } + ), DISPATCH: createRegistryControl( ( registry ) => ( { registryName, actionName, args } ) => { @@ -138,4 +152,7 @@ const controls = { ), }; +const getState = ( registry ) => + registry.stores[ 'core/edit-navigation' ].store.getState(); + export default controls; diff --git a/packages/edit-navigation/src/store/reducer.js b/packages/edit-navigation/src/store/reducer.js index dedeb4208e3e82..db8d072af98bc6 100644 --- a/packages/edit-navigation/src/store/reducer.js +++ b/packages/edit-navigation/src/store/reducer.js @@ -3,29 +3,37 @@ */ import { combineReducers } from '@wordpress/data'; -function processingQueue( state, { type, id, ...rest } ) { +function mapping( state, { type, postId, ...rest } ) { + if ( type === 'SET_MENU_ITEM_TO_CLIENT_ID_MAPPING' ) { + state[ postId ] = rest.mapping; + } + + return state || {}; +} + +function processingQueue( state, { type, postId, ...rest } ) { switch ( type ) { case 'START_PROCESSING_POST': - state[ id ] = { - ...state[ id ], + state[ postId ] = { + ...state[ postId ], inProgress: true, }; break; case 'FINISH_PROCESSING_POST': - state[ id ] = { - ...state[ id ], + state[ postId ] = { + ...state[ postId ], inProgress: false, pendingActions: - state[ id ]?.pendingActions?.filter( + state[ postId ]?.pendingActions?.filter( ( item ) => item !== rest.action ) || [], }; break; case 'ENQUEUE_AFTER_PROCESSING': - const pendingActions = state[ id ]?.pendingActions || []; + const pendingActions = state[ postId ]?.pendingActions || []; if ( ! pendingActions.includes( rest.action ) ) { - state[ id ] = { - ...state[ id ], + state[ postId ] = { + ...state[ postId ], pendingActions: [ ...pendingActions, rest.action ], }; } @@ -36,5 +44,6 @@ function processingQueue( state, { type, id, ...rest } ) { } export default combineReducers( { + mapping, processingQueue, } ); diff --git a/packages/edit-navigation/src/store/resolvers.js b/packages/edit-navigation/src/store/resolvers.js index a4c44a7771e520..9c7543eb913efb 100644 --- a/packages/edit-navigation/src/store/resolvers.js +++ b/packages/edit-navigation/src/store/resolvers.js @@ -16,15 +16,22 @@ import { KIND, POST_TYPE, buildNavigationPostId } from './utils'; export function* getNavigationPost( menuId ) { // Ensure an empty post to start with - yield persistPost( - createStubPost( menuId, [], { - isResolving: true, - } ) - ); + const stubPost = createStubPost( menuId, null, { + isResolving: true, + } ); + yield persistPost( stubPost ); // Now let's create a proper one hydrated with actual menu items const menuItems = yield resolveMenuItems( menuId ); - yield persistPost( createStubPost( menuId, menuItems ) ); + const [ navigationBlock, menuItemIdToClientId ] = createNavigationBlock( + menuItems + ); + yield { + type: 'SET_MENU_ITEM_TO_CLIENT_ID_MAPPING', + postId: stubPost.id, + mapping: menuItemIdToClientId, + }; + yield persistPost( createStubPost( menuId, navigationBlock ) ); } const persistPost = ( post ) => @@ -38,10 +45,7 @@ const persistPost = ( post ) => false ); -const createStubPost = ( menuId, menuItems, meta ) => { - const [ navigationBlock, menuItemIdToClientId ] = createNavigationBlock( - menuItems - ); +const createStubPost = ( menuId, navigationBlock, meta ) => { const id = buildNavigationPostId( menuId ); return { id, @@ -52,7 +56,6 @@ const createStubPost = ( menuId, menuItems, meta ) => { meta: { ...meta, menuId, - menuItemIdToClientId, }, }; }; diff --git a/packages/edit-navigation/src/store/selectors.js b/packages/edit-navigation/src/store/selectors.js index fb5c82e1cf42da..2b9dc31f2c0ed9 100644 --- a/packages/edit-navigation/src/store/selectors.js +++ b/packages/edit-navigation/src/store/selectors.js @@ -29,8 +29,8 @@ export const isResolvingNavigationPost = ( state, menuId ) => { }; export const getMenuItemForClientId = createRegistrySelector( - ( select ) => ( state, post, clientId ) => { - const mapping = invert( post.meta.menuItemIdToClientId ); + ( select ) => ( state, postId, clientId ) => { + const mapping = invert( state.mapping[ postId ] ); return select( 'core' ).getMenuItem( mapping[ clientId ] ); } );