diff --git a/packages/edit-site/src/components/navigation-inspector/index.js b/packages/edit-site/src/components/navigation-inspector/index.js index 0034362581ddd..e25cb30d7b180 100644 --- a/packages/edit-site/src/components/navigation-inspector/index.js +++ b/packages/edit-site/src/components/navigation-inspector/index.js @@ -2,13 +2,11 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useState, useEffect } from '@wordpress/element'; import { store as coreStore, useEntityBlockEditor } from '@wordpress/core-data'; import { store as blockEditorStore, BlockEditorProvider, - BlockTools, - BlockList, } from '@wordpress/block-editor'; import { speak } from '@wordpress/a11y'; import { __ } from '@wordpress/i18n'; @@ -18,91 +16,130 @@ import { __ } from '@wordpress/i18n'; */ import NavigationMenu from './navigation-menu'; -const NAVIGATION_MENUS_QUERY = [ - 'postType', - 'wp_navigation', - [ { per_page: 1, status: 'publish' } ], -]; - -function NavigationBlockEditorLoader( { onSelect, navigationPostId } ) { - const [ innerBlocks, onInput, onChange ] = useEntityBlockEditor( - 'postType', - 'wp_navigation', - { id: navigationPostId } - ); - return ( - - -
- - - -
-
- ); -} +const NAVIGATION_MENUS_QUERY = [ { per_page: -1, status: 'publish' } ]; export default function NavigationInspector( { onSelect } ) { const { - navigationBlockId, - navigationPostId, - isLoadingInnerBlocks, - hasLoadedInnerBlocks, + selectedNavigationBlockId, + clientIdToRef, + navigationMenus, + isResolvingNavigationMenus, + hasResolvedNavigationMenus, + firstNavigationBlockId, } = useSelect( ( select ) => { const { __experimentalGetActiveBlockIdByBlockNames, __experimentalGetGlobalBlocksByName, getBlock, } = select( blockEditorStore ); - const { isResolving, hasFinishedResolution, getEntityRecords } = + + const { getEntityRecords, hasFinishedResolution, isResolving } = select( coreStore ); - const selectedNavBlockId = - __experimentalGetActiveBlockIdByBlockNames( 'core/navigation' ) || - __experimentalGetGlobalBlocksByName( 'core/navigation' )?.[ 0 ]; - - let navigationPost, isLoading, hasLoaded; - if ( selectedNavBlockId ) { - navigationPost = getBlock( selectedNavBlockId )?.attributes?.ref; - isLoading = isResolving( 'getEntityRecord', [ - 'postType', - 'wp_navigation', - navigationPost, - ] ); - hasLoaded = hasFinishedResolution( 'getEntityRecord', [ - 'postType', - 'wp_navigation', - navigationPost, - ] ); - } else { - const navigationMenus = getEntityRecords( - ...NAVIGATION_MENUS_QUERY - ); - if ( navigationMenus?.length > 0 ) { - navigationPost = navigationMenus[ 0 ].id; - } - isLoading = isResolving( - 'getEntityRecords', - NAVIGATION_MENUS_QUERY - ); - hasLoaded = hasFinishedResolution( - 'getEntityRecords', - NAVIGATION_MENUS_QUERY - ); - } + const navigationMenusQuery = [ + 'postType', + 'wp_navigation', + NAVIGATION_MENUS_QUERY[ 0 ], + ]; + + // Get the active Navigation block (if present). + const selectedNavId = + __experimentalGetActiveBlockIdByBlockNames( 'core/navigation' ); + // Get all Navigation blocks currently within the editor canvas. + const navBlockIds = + __experimentalGetGlobalBlocksByName( 'core/navigation' ); + const idToRef = {}; + navBlockIds.forEach( ( id ) => { + idToRef[ id ] = getBlock( id )?.attributes?.ref; + } ); return { - navigationPostId: navigationPost, - navigationBlockId: selectedNavBlockId, - isLoadingInnerBlocks: isLoading, - hasLoadedInnerBlocks: hasLoaded, + selectedNavigationBlockId: selectedNavId, + firstNavigationBlockId: navBlockIds?.[ 0 ], + clientIdToRef: idToRef, + navigationMenus: getEntityRecords( ...navigationMenusQuery ), + isResolvingNavigationMenus: isResolving( + 'getEntityRecords', + navigationMenusQuery + ), + hasResolvedNavigationMenus: hasFinishedResolution( + 'getEntityRecords', + navigationMenusQuery + ), }; }, [] ); + const firstNavRefInTemplate = clientIdToRef[ firstNavigationBlockId ]; + const firstNavigationMenuRef = navigationMenus?.[ 0 ]?.id; + + // Default Navigation Menu is either: + // - the Navigation Menu referenced by the first Nav block within the template. + // - the first of the available Navigation Menus (`wp_navigation`) posts. + const defaultNavigationMenuId = + firstNavRefInTemplate || firstNavigationMenuRef; + + // The Navigation Menu manually selected by the user within the Nav inspector. + const [ currentMenuId, setCurrentMenuId ] = useState( + firstNavRefInTemplate + ); + + // If a Nav block is selected within the canvas then set the + // Navigation Menu referenced by it's `ref` attribute to be + // active within the Navigation sidebar. + useEffect( () => { + if ( selectedNavigationBlockId ) { + setCurrentMenuId( clientIdToRef[ selectedNavigationBlockId ] ); + } + }, [ selectedNavigationBlockId ] ); + + const [ innerBlocks, onInput, onChange ] = useEntityBlockEditor( + 'postType', + 'wp_navigation', + { id: currentMenuId || defaultNavigationMenuId } + ); + + const { isLoadingInnerBlocks, hasLoadedInnerBlocks } = useSelect( + ( select ) => { + const { isResolving, hasFinishedResolution } = select( coreStore ); + return { + isLoadingInnerBlocks: isResolving( 'getEntityRecord', [ + 'postType', + 'wp_navigation', + currentMenuId || defaultNavigationMenuId, + ] ), + hasLoadedInnerBlocks: hasFinishedResolution( + 'getEntityRecord', + [ + 'postType', + 'wp_navigation', + currentMenuId || defaultNavigationMenuId, + ] + ), + }; + }, + [ currentMenuId, defaultNavigationMenuId ] + ); + + const isLoading = ! ( hasResolvedNavigationMenus && hasLoadedInnerBlocks ); + + const hasNavigationMenus = !! navigationMenus?.length; + + // Entity block editor will return entities that are not currently published. + // Guard by only allowing their usage if there are published Nav Menus. + const publishedInnerBlocks = hasNavigationMenus ? innerBlocks : []; + + const hasInnerBlocks = !! publishedInnerBlocks?.length; + + useEffect( () => { + if ( isResolvingNavigationMenus ) { + speak( 'Loading Navigation sidebar menus.' ); + } + + if ( hasResolvedNavigationMenus ) { + speak( 'Navigation sidebar menus have loaded.' ); + } + }, [ isResolvingNavigationMenus, hasResolvedNavigationMenus ] ); + useEffect( () => { if ( isLoadingInnerBlocks ) { speak( 'Loading Navigation sidebar selected menu items.' ); @@ -115,39 +152,37 @@ export default function NavigationInspector( { onSelect } ) { return (
- { hasLoadedInnerBlocks && - ! isLoadingInnerBlocks && - ! navigationBlockId && - ! navigationPostId && ( -

- { __( 'There are no Navigation Menus.' ) } -

- ) } - - { ! hasLoadedInnerBlocks && ( + { hasResolvedNavigationMenus && ! hasNavigationMenus && ( +

+ { __( 'There are no Navigation Menus.' ) } +

+ ) } + + { ! hasResolvedNavigationMenus && (
) } - { isLoadingInnerBlocks && ( + { isLoading && ( <>
) } - { navigationBlockId && navigationPostId && hasLoadedInnerBlocks && ( - + { hasInnerBlocks && ! isLoading && ( + + + + ) } + + { ! hasInnerBlocks && ! isLoading && ( +

+ { __( 'Navigation Menu is empty.' ) } +

) } - { ! navigationBlockId && - navigationPostId && - hasLoadedInnerBlocks && ( - - ) }
); } diff --git a/packages/edit-site/src/components/navigation-inspector/navigation-menu.js b/packages/edit-site/src/components/navigation-inspector/navigation-menu.js index 7e2255f874ddb..56ad6b9fac762 100644 --- a/packages/edit-site/src/components/navigation-inspector/navigation-menu.js +++ b/packages/edit-site/src/components/navigation-inspector/navigation-menu.js @@ -4,30 +4,71 @@ import { privateApis as blockEditorPrivateApis, store as blockEditorStore, + BlockList, + BlockTools, } from '@wordpress/block-editor'; -import { useSelect } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ import { unlock } from '../../private-apis'; -/** - * Experimental dependencies - */ -const { OffCanvasEditor, LeafMoreMenu } = unlock( blockEditorPrivateApis ); +const ALLOWED_BLOCKS = { + 'core/navigation': [ + 'core/navigation-link', + 'core/search', + 'core/social-links', + 'core/page-list', + 'core/spacer', + 'core/home-link', + 'core/site-title', + 'core/site-logo', + 'core/navigation-submenu', + ], + 'core/social-links': [ 'core/social-link' ], + 'core/navigation-submenu': [ + 'core/navigation-link', + 'core/navigation-submenu', + ], + 'core/navigation-link': [ + 'core/navigation-link', + 'core/navigation-submenu', + ], + 'core/page-list': [ 'core/page-list-item' ], +}; -export default function NavigationMenu( { onSelect, navigationBlockId } ) { - const { clientIdsTree } = useSelect( - ( select ) => { - const { __unstableGetClientIdsTree } = select( blockEditorStore ); - return { - clientIdsTree: __unstableGetClientIdsTree( navigationBlockId ), - }; - }, - [ navigationBlockId ] - ); +export default function NavigationMenu( { onSelect } ) { + const { clientIdsTree, innerBlocks } = useSelect( ( select ) => { + const { __unstableGetClientIdsTree, getBlocks } = + select( blockEditorStore ); + return { + clientIdsTree: __unstableGetClientIdsTree(), + innerBlocks: getBlocks(), + }; + } ); + const { updateBlockListSettings } = useDispatch( blockEditorStore ); + + const { OffCanvasEditor, LeafMoreMenu } = unlock( blockEditorPrivateApis ); + + //TODO: Block settings are normally updated as a side effect of rendering InnerBlocks in BlockList + //Think through a better way of doing this, possible with adding allowed blocks to block library metadata + useEffect( () => { + updateBlockListSettings( '', { + allowedBlocks: ALLOWED_BLOCKS[ 'core/navigation' ], + } ); + innerBlocks.forEach( ( block ) => { + if ( ALLOWED_BLOCKS[ block.name ] ) { + updateBlockListSettings( block.clientId, { + allowedBlocks: ALLOWED_BLOCKS[ block.name ], + } ); + } + } ); + }, [ updateBlockListSettings, innerBlocks ] ); + // The hidden block is needed because it makes block edit side effects trigger. + // For example a navigation page list load its items has an effect on edit to load its items. return ( <> +
+ + + +
); }