Skip to content

Commit

Permalink
Separate concerns in useNavigationBlocks (#22825)
Browse files Browse the repository at this point in the history
* First stab at encapsulation of useNavigationBlocks

* Flicker issue solved again

* Encapsulated parts of the logic from useNavigationBlocks

* Formatting

* Decouple fetching the data, transforming, saving, and creating draft posts

* Move blocks argument to useSaveNavigationBlocks hook

* Separate fetching menu items from creating navigation blocks

* Move PromiseQueue to use-create-missing-menu-items as it is only used there

* construct query inline instead of having useMenuItemsQuery

* Simplify the refactor (thank you Andrei!)

* Extract PromiseQueue into a separate file

* Move eventuallySaveItems to useMenuItems

* Update packages/edit-navigation/src/components/menu-editor/use-create-missing-menu-items.js

Co-authored-by: andrei draganescu <[email protected]>

* Update packages/edit-navigation/src/components/menu-editor/use-create-missing-menu-items.js

Co-authored-by: andrei draganescu <[email protected]>

* Show the notice after receiveEntityRecords

* Fix spacing issue

* Remove rogue async

* rename linkBlocks to blocks

Co-authored-by: andrei draganescu <[email protected]>
  • Loading branch information
adamziel and draganescu authored Jun 2, 2020
1 parent 77fb18e commit ab878d5
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const flattenBlocks = ( blocks ) =>
blocks.flatMap( ( item ) =>
[ item ].concat( flattenBlocks( item.innerBlocks || [] ) )
);
24 changes: 20 additions & 4 deletions packages/edit-navigation/src/components/menu-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
BlockEditorProvider,
} from '@wordpress/block-editor';
import { useViewportMatch } from '@wordpress/compose';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import useMenuItems from './use-menu-items';
import useNavigationBlocks from './use-navigation-blocks';
import MenuEditorShortcuts from './shortcuts';
import BlockEditorPanel from './block-editor-panel';
Expand All @@ -20,8 +22,19 @@ export default function MenuEditor( {
blockEditorSettings,
onDeleteMenu,
} ) {
const [ blocks, setBlocks, saveBlocks ] = useNavigationBlocks( menuId );
const isLargeViewport = useViewportMatch( 'medium' );
const query = useMemo( () => ( { menus: menuId, per_page: -1 } ), [
menuId,
] );
const {
menuItems,
eventuallySaveMenuItems,
createMissingMenuItems,
} = useMenuItems( query );
const { blocks, setBlocks, menuItemsRef } = useNavigationBlocks(
menuItems
);
const saveMenuItems = () => eventuallySaveMenuItems( blocks, menuItemsRef );

return (
<div className="edit-navigation-menu-editor">
Expand All @@ -31,21 +44,24 @@ export default function MenuEditor( {
<BlockEditorProvider
value={ blocks }
onInput={ ( updatedBlocks ) => setBlocks( updatedBlocks ) }
onChange={ ( updatedBlocks ) => setBlocks( updatedBlocks ) }
onChange={ ( updatedBlocks ) => {
createMissingMenuItems( updatedBlocks, menuItemsRef );
setBlocks( updatedBlocks );
} }
settings={ {
...blockEditorSettings,
templateLock: 'all',
hasFixedToolbar: true,
} }
>
<BlockEditorKeyboardShortcuts />
<MenuEditorShortcuts saveBlocks={ saveBlocks } />
<MenuEditorShortcuts saveBlocks={ saveMenuItems } />
<NavigationStructurePanel
blocks={ blocks }
initialOpen={ isLargeViewport }
/>
<BlockEditorPanel
saveBlocks={ saveBlocks }
saveBlocks={ saveMenuItems }
menuId={ menuId }
onDeleteMenu={ onDeleteMenu }
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import { useRef, useCallback } from '@wordpress/element';

/**
* Internal dependencies
*/
import { flattenBlocks } from './helpers';
import PromiseQueue from './promise-queue';

/**
* When a new Navigation child block is added, we create a draft menuItem for it because
* the batch save endpoint expects all the menu items to have a valid id already.
* PromiseQueue is used in order to
* 1) limit the amount of requests processed at the same time
* 2) save the menu only after all requests are finalized
*
* @return {function(*=): void} Function registering it's argument to be called once all menuItems are created.
*/
export default function useCreateMissingMenuItems() {
const promiseQueueRef = useRef( new PromiseQueue() );
const enqueuedBlocksIds = useRef( [] );
const createMissingMenuItems = ( blocks, menuItemsRef ) => {
for ( const { clientId, name } of flattenBlocks( blocks ) ) {
// No need to create menuItems for the wrapping navigation block
if ( name === 'core/navigation' ) {
continue;
}
// Menu item was already created
if ( clientId in menuItemsRef.current ) {
continue;
}
// Menu item already in the queue
if ( enqueuedBlocksIds.current.includes( clientId ) ) {
continue;
}
enqueuedBlocksIds.current.push( clientId );
promiseQueueRef.current.enqueue( () =>
createDraftMenuItem( clientId ).then( ( menuItem ) => {
menuItemsRef.current[ clientId ] = menuItem;
enqueuedBlocksIds.current.splice(
enqueuedBlocksIds.current.indexOf( clientId )
);
} )
);
}
};
const onCreated = useCallback(
( callback ) => promiseQueueRef.current.then( callback ),
[ promiseQueueRef.current ]
);
return { createMissingMenuItems, onCreated };
}

function createDraftMenuItem() {
return apiFetch( {
path: `/__experimental/menu-items`,
method: 'POST',
data: {
title: 'Placeholder',
url: 'Placeholder',
menu_order: 0,
},
} );
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,77 @@ import { keyBy, omit } from 'lodash';
/**
* WordPress dependencies
*/
import { useDispatch, useSelect } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';

export default async function batchSave(
menuId,
menuItemsRef,
navigationBlock
) {
/**
* Internal dependencies
*/
import useCreateMissingMenuItems from './use-create-missing-menu-items';

export default function useMenuItems( query ) {
const menuItems = useFetchMenuItems( query );
const saveMenuItems = useSaveMenuItems( query );
const { createMissingMenuItems, onCreated } = useCreateMissingMenuItems();
const eventuallySaveMenuItems = ( blocks, menuItemsRef ) =>
onCreated( () => saveMenuItems( blocks, menuItemsRef ) );
return { menuItems, eventuallySaveMenuItems, createMissingMenuItems };
}

export function useFetchMenuItems( query ) {
const { menuItems, isResolving } = useSelect( ( select ) => ( {
menuItems: select( 'core' ).getMenuItems( query ),
isResolving: select( 'core/data' ).isResolving(
'core',
'getMenuItems',
[ query ]
),
} ) );

const [ resolvedMenuItems, setResolvedMenuItems ] = useState( null );

useEffect( () => {
if ( isResolving || menuItems === null ) {
return;
}

setResolvedMenuItems( menuItems );
}, [ isResolving, menuItems ] );

return resolvedMenuItems;
}

export function useSaveMenuItems( query ) {
const { receiveEntityRecords } = useDispatch( 'core' );
const { createSuccessNotice, createErrorNotice } = useDispatch(
'core/notices'
);

const saveBlocks = async ( blocks, menuItemsRef ) => {
const result = await batchSave(
query.menus,
menuItemsRef,
blocks[ 0 ]
);

if ( result.success ) {
receiveEntityRecords( 'root', 'menuItem', [], query, true );
createSuccessNotice( __( 'Navigation saved.' ), {
type: 'snackbar',
} );
} else {
createErrorNotice( __( 'There was an error.' ), {
type: 'snackbar',
} );
}
};

return saveBlocks;
}

async function batchSave( menuId, menuItemsRef, navigationBlock ) {
const { nonce, stylesheet } = await apiFetch( {
path: '/__experimental/customizer-nonces/get-save-nonce',
} );
Expand Down
Loading

0 comments on commit ab878d5

Please sign in to comment.