diff --git a/packages/edit-navigation/src/components/layout/index.js b/packages/edit-navigation/src/components/layout/index.js index 304657989176cd..6cb4be9099693d 100644 --- a/packages/edit-navigation/src/components/layout/index.js +++ b/packages/edit-navigation/src/components/layout/index.js @@ -28,7 +28,6 @@ import { __ } from '@wordpress/i18n'; import UnselectedMenuState from './unselected-menu-state'; import { IsMenuNameControlFocusedContext, - MenuIdContext, useNavigationEditor, useNavigationBlockEditor, useMenuNotifications, @@ -80,15 +79,6 @@ export default function Layout( { blockEditorSettings } ) { navigationPost ); - const [ isMenuLoaded, setIsMenuLoaded ] = useState( false ); - - useEffect( () => { - if ( ! isMenuLoaded && menus?.length ) { - setIsMenuLoaded( true ); - selectMenu( menus[ 0 ].id ); - } - }, [ menus ] ); - const { hasSidebarEnabled } = useSelect( ( select ) => ( { hasSidebarEnabled: !! select( @@ -102,7 +92,7 @@ export default function Layout( { blockEditorSettings } ) { if ( ! selectedMenuId && menus?.length ) { selectMenu( menus[ 0 ].id ); } - }, [] ); + }, [ selectedMenuId, menus ] ); useMenuNotifications( selectedMenuId ); @@ -136,106 +126,89 @@ export default function Layout( { blockEditorSettings } ) { } } useSubRegistry={ false } > - - [ - isMenuNameControlFocused, - setIsMenuNameControlFocused, - ], - [ isMenuNameControlFocused ] - ) } - > - - } - content={ - <> - { ! hasFinishedInitialLoad && ( - - ) } + [ + isMenuNameControlFocused, + setIsMenuNameControlFocused, + ], + [ isMenuNameControlFocused ] + ) } + > + + } + content={ + <> + { ! hasFinishedInitialLoad && } - { ! isMenuSelected && - hasFinishedInitialLoad && ( - + ) } + { isBlockEditorReady && ( + <> + +
+ + - ) } - { isBlockEditorReady && ( - <> - -
- - -
- - ) } - - } - sidebar={ - ( hasPermanentSidebar || - hasSidebarEnabled ) && ( - - ) - } - /> - - - +
+ + ) } + + } + sidebar={ + ( hasPermanentSidebar || + hasSidebarEnabled ) && ( + + ) + } + /> + +
diff --git a/packages/edit-navigation/src/components/name-display/index.js b/packages/edit-navigation/src/components/name-display/index.js index 5e60729f49e688..d6973bdad761c6 100644 --- a/packages/edit-navigation/src/components/name-display/index.js +++ b/packages/edit-navigation/src/components/name-display/index.js @@ -10,13 +10,13 @@ import { BlockControls } from '@wordpress/block-editor'; import { untitledMenu, useMenuEntity, - useSelectedMenuData, + useSelectedMenuId, IsMenuNameControlFocusedContext, } from '../../hooks'; import { sprintf, __ } from '@wordpress/i18n'; export default function NameDisplay() { - const { menuId } = useSelectedMenuData(); + const [ menuId ] = useSelectedMenuId(); const { editedMenu } = useMenuEntity( menuId ); const [ , setIsMenuNameEditFocused ] = useContext( IsMenuNameControlFocusedContext diff --git a/packages/edit-navigation/src/components/name-editor/index.js b/packages/edit-navigation/src/components/name-editor/index.js index c078754faca0e7..e5c0c4ec3084bf 100644 --- a/packages/edit-navigation/src/components/name-editor/index.js +++ b/packages/edit-navigation/src/components/name-editor/index.js @@ -11,7 +11,7 @@ import { IsMenuNameControlFocusedContext, untitledMenu, useMenuEntity, - useSelectedMenuData, + useSelectedMenuId, } from '../../hooks'; export function NameEditor() { @@ -19,7 +19,7 @@ export function NameEditor() { IsMenuNameControlFocusedContext ); - const { menuId } = useSelectedMenuData(); + const [ menuId ] = useSelectedMenuId(); const { editedMenu, editMenuEntityRecord, menuEntityData } = useMenuEntity( menuId ); diff --git a/packages/edit-navigation/src/hooks/index.js b/packages/edit-navigation/src/hooks/index.js index 5d2c4f94745657..b0ad3b18b1f5fc 100644 --- a/packages/edit-navigation/src/hooks/index.js +++ b/packages/edit-navigation/src/hooks/index.js @@ -5,12 +5,11 @@ import { __ } from '@wordpress/i18n'; import { createContext } from '@wordpress/element'; export const untitledMenu = __( '(untitled menu)' ); -export const MenuIdContext = createContext(); export const IsMenuNameControlFocusedContext = createContext(); export { default as useMenuEntity } from './use-menu-entity'; export { default as useNavigationEditor } from './use-navigation-editor'; export { default as useNavigationBlockEditor } from './use-navigation-block-editor'; export { default as useMenuNotifications } from './use-menu-notifications'; -export { default as useSelectedMenuData } from './use-selected-menu-data'; +export { default as useSelectedMenuId } from './use-selected-menu-id'; export { default as useMenuLocations } from './use-menu-locations'; diff --git a/packages/edit-navigation/src/hooks/use-menu-locations.js b/packages/edit-navigation/src/hooks/use-menu-locations.js index d1512a60c9eeb3..9879db10689184 100644 --- a/packages/edit-navigation/src/hooks/use-menu-locations.js +++ b/packages/edit-navigation/src/hooks/use-menu-locations.js @@ -11,7 +11,7 @@ import { merge } from 'lodash'; /** * Internal dependencies */ -import { useMenuEntity, useSelectedMenuData } from './index'; +import { useMenuEntity, useSelectedMenuId } from './index'; const locationsForMenuId = ( menuLocationsByName, id ) => Object.values( menuLocationsByName ) @@ -21,7 +21,7 @@ const locationsForMenuId = ( menuLocationsByName, id ) => export default function useMenuLocations() { const [ menuLocationsByName, setMenuLocationsByName ] = useState( null ); - const { menuId } = useSelectedMenuData(); + const [ menuId ] = useSelectedMenuId(); const { editMenuEntityRecord, menuEntityData } = useMenuEntity( menuId ); useEffect( () => { let isMounted = true; diff --git a/packages/edit-navigation/src/hooks/use-navigation-editor.js b/packages/edit-navigation/src/hooks/use-navigation-editor.js index 06b4065e822efa..2112582a29c94c 100644 --- a/packages/edit-navigation/src/hooks/use-navigation-editor.js +++ b/packages/edit-navigation/src/hooks/use-navigation-editor.js @@ -1,14 +1,17 @@ /** * WordPress dependencies */ +import { __, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { useState, useEffect } from '@wordpress/element'; +import { store as noticesStore } from '@wordpress/notices'; +import { store as coreStore } from '@wordpress/core-data'; + /** * Internal dependencies */ import { store as editNavigationStore } from '../store'; -import { store as noticesStore } from '@wordpress/notices'; -import { __, sprintf } from '@wordpress/i18n'; +import { useSelectedMenuId } from './index'; const getMenusData = ( select ) => { const selectors = select( 'core' ); @@ -29,18 +32,17 @@ export default function useNavigationEditor() { true, false, ].map( ( bool ) => () => setIsManageLocationsModalOpen( bool ) ); - const { deleteMenu: _deleteMenu } = useDispatch( 'core' ); - const [ selectedMenuId, setSelectedMenuId ] = useState( null ); + const { deleteMenu: _deleteMenu } = useDispatch( coreStore ); + const [ selectedMenuId, setSelectedMenuId ] = useSelectedMenuId(); const [ hasFinishedInitialLoad, setHasFinishedInitialLoad ] = useState( false ); const { menus, hasLoadedMenus } = useSelect( getMenusData, [] ); - const [ isMenuSelected, setIsMenuSelected ] = useState( true ); const { createErrorNotice, createInfoNotice } = useDispatch( noticesStore ); const isMenuBeingDeleted = useSelect( ( select ) => - select( 'core' ).isDeletingEntityRecord( + select( coreStore ).isDeletingEntityRecord( 'root', 'menu', selectedMenuId @@ -73,7 +75,7 @@ export default function useNavigationEditor() { force: true, } ); if ( didDeleteMenu ) { - setSelectedMenuId( null ); + setSelectedMenuId( 0 ); createInfoNotice( sprintf( // translators: %s: the name of a menu. @@ -90,9 +92,6 @@ export default function useNavigationEditor() { } }; - useEffect( () => setIsMenuSelected( selectedMenuId !== null ), [ - selectedMenuId, - ] ); return { menus, hasLoadedMenus, @@ -105,6 +104,6 @@ export default function useNavigationEditor() { openManageLocationsModal, closeManageLocationsModal, isManageLocationsModalOpen, - isMenuSelected, + isMenuSelected: !! selectedMenuId, }; } diff --git a/packages/edit-navigation/src/hooks/use-selected-menu-data.js b/packages/edit-navigation/src/hooks/use-selected-menu-data.js deleted file mode 100644 index 7947bd49699659..00000000000000 --- a/packages/edit-navigation/src/hooks/use-selected-menu-data.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; -import { useContext } from '@wordpress/element'; -/** - * Internal dependencies - */ -import { MenuIdContext, untitledMenu } from './index'; - -export default function useSelectedMenuData() { - const menuId = useContext( MenuIdContext ); - const menu = useSelect( ( select ) => select( 'core' ).getMenu( menuId ), [ - menuId, - ] ); - const menuName = menu?.name ?? untitledMenu; - return { - menuId, - menu, - menuName, - }; -} diff --git a/packages/edit-navigation/src/hooks/use-selected-menu-id.js b/packages/edit-navigation/src/hooks/use-selected-menu-id.js new file mode 100644 index 00000000000000..aae5a4c95fd895 --- /dev/null +++ b/packages/edit-navigation/src/hooks/use-selected-menu-id.js @@ -0,0 +1,26 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as editNavigationStore } from '../store'; + +/** + * Returns selected menu ID and the setter. + * + * @return {[number, Function]} A tuple where first item is the + * selected menu ID and second is + * the setter. + */ +export default function useSelectedMenuId() { + const selectedMenuId = useSelect( + ( select ) => select( editNavigationStore ).getSelectedMenuId(), + [] + ); + const { setSelectedMenuId } = useDispatch( editNavigationStore ); + + return [ selectedMenuId, setSelectedMenuId ]; +} diff --git a/packages/edit-navigation/src/store/actions.js b/packages/edit-navigation/src/store/actions.js index 5af226c4471353..e7e94d65ac7fbf 100644 --- a/packages/edit-navigation/src/store/actions.js +++ b/packages/edit-navigation/src/store/actions.js @@ -28,6 +28,19 @@ import { const { ajaxurl } = window; +/** + * Returns an action object used to select menu. + * + * @param {number} menuId The menu ID. + * @return {Object} Action object. + */ +export function setSelectedMenuId( menuId ) { + return { + type: 'SET_SELECTED_MENU_ID', + menuId, + }; +} + /** * Creates a menu item for every block that doesn't have an associated menuItem. * Requests POST /wp/v2/menu-items once for every menu item created. diff --git a/packages/edit-navigation/src/store/index.js b/packages/edit-navigation/src/store/index.js index 6d3f4d5a401ee6..74c53af154d23b 100644 --- a/packages/edit-navigation/src/store/index.js +++ b/packages/edit-navigation/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { createReduxStore, register } from '@wordpress/data'; +import { createReduxStore, registerStore } from '@wordpress/data'; /** * Internal dependencies @@ -26,6 +26,7 @@ const storeConfig = { selectors, resolvers, actions, + persist: [ 'selectedMenuId' ], }; /** @@ -37,4 +38,6 @@ const storeConfig = { */ export const store = createReduxStore( STORE_NAME, storeConfig ); -register( store ); +// Once we build a more generic persistence plugin that works across types of stores +// we'd be able to replace this with a register call. +registerStore( STORE_NAME, storeConfig ); diff --git a/packages/edit-navigation/src/store/reducer.js b/packages/edit-navigation/src/store/reducer.js index 544857b159af27..8511ccd03d0496 100644 --- a/packages/edit-navigation/src/store/reducer.js +++ b/packages/edit-navigation/src/store/reducer.js @@ -81,7 +81,25 @@ export function processingQueue( state, action ) { return state || {}; } +/** + * Reducer keeping track of selected menu ID. + * + * @param {number} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function selectedMenuId( state = 0, action ) { + switch ( action.type ) { + case 'SET_SELECTED_MENU_ID': + return action.menuId; + } + + return state; +} + export default combineReducers( { mapping, processingQueue, + selectedMenuId, } ); diff --git a/packages/edit-navigation/src/store/selectors.js b/packages/edit-navigation/src/store/selectors.js index 13c496c7b028ed..9cfbed52cc8642 100644 --- a/packages/edit-navigation/src/store/selectors.js +++ b/packages/edit-navigation/src/store/selectors.js @@ -12,9 +12,19 @@ import { createRegistrySelector } from '@wordpress/data'; * Internal dependencies */ import { NAVIGATION_POST_KIND, NAVIGATION_POST_POST_TYPE } from '../constants'; - import { buildNavigationPostId } from './utils'; +/** + * Returns the selected menu ID. + * + * @param {Object} state Global application state. + * + * @return {number} The selected menu ID. + */ +export function getSelectedMenuId( state ) { + return state.selectedMenuId ?? 0; +} + /** * Returns a "stub" navigation post reflecting the contents of menu with id=menuId. The * post is meant as a convenient to only exists in runtime and should never be saved. It diff --git a/packages/edit-navigation/src/store/test/actions.js b/packages/edit-navigation/src/store/test/actions.js index d3e1bbf4a8ab5b..c3e90091fccb28 100644 --- a/packages/edit-navigation/src/store/test/actions.js +++ b/packages/edit-navigation/src/store/test/actions.js @@ -7,7 +7,11 @@ import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ -import { createMissingMenuItems, saveNavigationPost } from '../actions'; +import { + createMissingMenuItems, + saveNavigationPost, + setSelectedMenuId, +} from '../actions'; import { resolveMenuItems, getMenuItemToClientIdMapping, @@ -622,3 +626,13 @@ describe( 'saveNavigationPost', () => { ); } ); } ); + +describe( 'setSelectedMenuId', () => { + it( 'should return the SET_SELECTED_MENU_ID action', () => { + const menuId = 1; + expect( setSelectedMenuId( menuId ) ).toEqual( { + type: 'SET_SELECTED_MENU_ID', + menuId, + } ); + } ); +} ); diff --git a/packages/edit-navigation/src/store/test/reducer.js b/packages/edit-navigation/src/store/test/reducer.js index 7eac08dae705c8..d987bedf6f1e88 100644 --- a/packages/edit-navigation/src/store/test/reducer.js +++ b/packages/edit-navigation/src/store/test/reducer.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { mapping, processingQueue } from '../reducer'; +import { mapping, processingQueue, selectedMenuId } from '../reducer'; describe( 'mapping', () => { it( 'should initialize empty mapping when there is no original state', () => { @@ -176,3 +176,15 @@ describe( 'processingQueue', () => { } ); } ); } ); + +describe( 'selectedMenuId', () => { + it( 'should apply default state', () => { + expect( selectedMenuId( undefined, {} ) ).toEqual( 0 ); + } ); + + it( 'should update when a new menu is selected', () => { + expect( + selectedMenuId( 1, { type: 'SET_SELECTED_MENU_ID', menuId: 2 } ) + ).toBe( 2 ); + } ); +} ); diff --git a/packages/edit-navigation/src/store/test/selectors.js b/packages/edit-navigation/src/store/test/selectors.js index 40abbb893a8a17..50eae47e959fd7 100644 --- a/packages/edit-navigation/src/store/test/selectors.js +++ b/packages/edit-navigation/src/store/test/selectors.js @@ -5,6 +5,7 @@ import { getNavigationPostForMenu, hasResolvedNavigationPost, getMenuItemForClientId, + getSelectedMenuId, } from '../selectors'; import { NAVIGATION_POST_KIND, @@ -130,3 +131,15 @@ describe( 'getMenuItemForClientId', () => { getMenuItemForClientId.registry = defaultRegistry; } ); } ); + +describe( 'getSelectedMenuId', () => { + it( 'returns default selected menu ID (zero)', () => { + const state = {}; + expect( getSelectedMenuId( state ) ).toBe( 0 ); + } ); + + it( 'returns selected menu ID', () => { + const state = { selectedMenuId: 10 }; + expect( getSelectedMenuId( state ) ).toBe( 10 ); + } ); +} );