Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation Editor: Allow menu renaming #29012

Merged
merged 23 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import useMenuLocations from './use-menu-locations';
import useMenuLocations from '../../hooks/use-menu-locations';

export default function ManageLocations() {
const menus = useSelect( ( select ) => select( 'core' ).getMenus(), [] );
Expand Down
5 changes: 5 additions & 0 deletions packages/edit-navigation/src/components/header/save-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies
*/
import { store as editNavigationStore } from '../../store';
import { useMenuEntity, MenuIdContext } from '../../hooks';
import { useContext } from '@wordpress/element';

export default function SaveButton( { navigationPost } ) {
const menuId = useContext( MenuIdContext );
const { saveMenuName } = useMenuEntity( menuId );
const { saveNavigationPost } = useDispatch( editNavigationStore );

return (
Expand All @@ -19,6 +23,7 @@ export default function SaveButton( { navigationPost } ) {
isPrimary
onClick={ () => {
saveNavigationPost( navigationPost );
saveMenuName();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't anticipate this, but both these lines result in the menu being saved. saveNavigationPost saves the menu via the customizer API (because the REST API doesn't quite work yet for saving menu items).

I'd be worried about race conditions here.

This is some technical debt, so not really something caused by your PR.

I don't have a solution available right now, will have to have a think about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the edge case when the race condition could really happen?

Copy link
Contributor

@talldan talldan Feb 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that one request might overwrite the other, since both API requests can modify the same database record.

I think the easiest solution right now would be to make sure the requests happen sequentially. This could be done by moving the saveEditedEntityRecord dispatch to be inside the saveNavigationPost action. Should be able to dispatch it with code like this, where the yield will wait for the request to complete before triggering the second request (the call to batchSave):

yield dispatch(
	'core',
	'saveEditedEntityRecord',
	'root',
	'menu',
	menuId
);

This makes sense actually, as it means the on-screen notices that are implemented in saveNavigationPost can be made so that the visible notices associated with saving are displayed at the right time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some further background. The way saving menus currently works is a temporary thing. Currently the batchSave function has been patched to use an endpoint that is currently used by the Customize > Menus screen. But that isn't part of the REST API, and not something we want to support long into the future for the navigation editor.

Ideally the whole system would use the WordPress REST APIs and the core-data package's entities. There are two things that need to happen:

} }
disabled={ ! navigationPost }
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { InspectorControls } from '@wordpress/block-editor';
*/
import AutoAddPagesPanel from './auto-add-pages-panel';
import DeleteMenuPanel from './delete-menu-panel';
import { NameEditor } from '../name-editor';
import { PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

export default function InspectorAdditions( { menuId, onDeleteMenu } ) {
const selectedBlock = useSelect(
Expand All @@ -22,8 +25,11 @@ export default function InspectorAdditions( { menuId, onDeleteMenu } ) {

return (
<InspectorControls>
<AutoAddPagesPanel menuId={ menuId } />
<DeleteMenuPanel onDeleteMenu={ onDeleteMenu } />
<PanelBody title={ __( 'Menu Settings' ) }>
<NameEditor />
<AutoAddPagesPanel menuId={ menuId } />
<DeleteMenuPanel onDeleteMenu={ onDeleteMenu } />
</PanelBody>
</InspectorControls>
);
}
111 changes: 64 additions & 47 deletions packages/edit-navigation/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ import {
BlockInspector,
__unstableUseBlockSelectionClearer as useBlockSelectionClearer,
} from '@wordpress/block-editor';
import { useRef } from '@wordpress/element';
import { useRef, useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import EmptyState from './empty-state';
import useNavigationEditor from './use-navigation-editor';
import useNavigationBlockEditor from './use-navigation-block-editor';
import useMenuNotifications from './use-menu-notifications';
import {
IsMenuEditorFocused,
MenuIdContext,
useNavigationEditor,
useNavigationBlockEditor,
useMenuNotifications,
} from '../../hooks';
import ErrorBoundary from '../error-boundary';
import NavigationEditorShortcuts from './shortcuts';
import Header from '../header';
Expand All @@ -39,7 +43,9 @@ import { store as editNavigationStore } from '../../store';
export default function Layout( { blockEditorSettings } ) {
const canvasRef = useRef();
useBlockSelectionClearer( canvasRef );

const [ isMenuNameEditFocused, setIsMenuNameEditFocused ] = useState(
false
);
const { saveNavigationPost } = useDispatch( editNavigationStore );
const savePost = () => saveNavigationPost( navigationPost );

Expand Down Expand Up @@ -76,51 +82,62 @@ export default function Layout( { blockEditorSettings } ) {
'has-block-inspector': isBlockEditorReady,
} ) }
>
<Header
isPending={ ! hasLoadedMenus }
menus={ menus }
selectedMenuId={ selectedMenuId }
onSelectMenu={ selectMenu }
navigationPost={ navigationPost }
/>
<MenuIdContext.Provider value={ selectedMenuId }>
<IsMenuEditorFocused.Provider
value={ [
grzim marked this conversation as resolved.
Show resolved Hide resolved
isMenuNameEditFocused,
setIsMenuNameEditFocused,
] }
>
<Header
isPending={ ! hasLoadedMenus }
menus={ menus }
selectedMenuId={ selectedMenuId }
onSelectMenu={ selectMenu }
navigationPost={ navigationPost }
/>

{ ! hasFinishedInitialLoad && <Spinner /> }
{ ! hasFinishedInitialLoad && <Spinner /> }

{ hasFinishedInitialLoad && ! hasMenus && (
<EmptyState />
) }
{ hasFinishedInitialLoad && ! hasMenus && (
<EmptyState />
) }

{ isBlockEditorReady && (
<BlockEditorProvider
value={ blocks }
onInput={ onInput }
onChange={ onChange }
settings={ {
...blockEditorSettings,
templateLock: 'all',
} }
useSubRegistry={ false }
>
<BlockEditorKeyboardShortcuts />
<NavigationEditorShortcuts
saveBlocks={ savePost }
/>
<div
className="edit-navigation-layout__canvas"
ref={ canvasRef }
>
<Editor
isPending={ ! hasLoadedMenus }
blocks={ blocks }
/>
</div>
<InspectorAdditions
menuId={ selectedMenuId }
onDeleteMenu={ deleteMenu }
/>
<BlockInspector bubblesVirtually={ false } />
</BlockEditorProvider>
) }
{ isBlockEditorReady && (
<BlockEditorProvider
value={ blocks }
onInput={ onInput }
onChange={ onChange }
settings={ {
...blockEditorSettings,
templateLock: 'all',
} }
useSubRegistry={ false }
>
<BlockEditorKeyboardShortcuts />
<NavigationEditorShortcuts
saveBlocks={ savePost }
/>
<div
className="edit-navigation-layout__canvas"
ref={ canvasRef }
>
<Editor
isPending={ ! hasLoadedMenus }
blocks={ blocks }
/>
</div>
<InspectorAdditions
menuId={ selectedMenuId }
onDeleteMenu={ deleteMenu }
/>
<BlockInspector
bubblesVirtually={ false }
/>
</BlockEditorProvider>
) }
</IsMenuEditorFocused.Provider>
</MenuIdContext.Provider>
</div>

<Popover.Slot />
Expand Down
24 changes: 24 additions & 0 deletions packages/edit-navigation/src/components/name-display/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* WordPress dependencies
*/
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
import { BlockControls } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import { useNavigationEditorMenu, IsMenuEditorFocused } from '../../hooks';
import { useContext } from '@wordpress/element';

export default function NameDisplay() {
const { menuName } = useNavigationEditorMenu();
const [ , setIsMenuNameEditFocused ] = useContext( IsMenuEditorFocused );
return (
<BlockControls>
<ToolbarGroup>
<ToolbarButton onClick={ setIsMenuNameEditFocused }>
grzim marked this conversation as resolved.
Show resolved Hide resolved
grzim marked this conversation as resolved.
Show resolved Hide resolved
{ menuName }
</ToolbarButton>
</ToolbarGroup>
</BlockControls>
);
}
66 changes: 66 additions & 0 deletions packages/edit-navigation/src/components/name-editor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useEffect, useRef, useState, useContext } from '@wordpress/element';
/**
* Internal dependencies
*/
import { TextControl } from '@wordpress/components';
import {
IsMenuEditorFocused,
useMenuEntity,
useNavigationEditorMenu,
} from '../../hooks';
import { useInstanceId } from '@wordpress/compose';

export function NameEditor() {
const [ isMenuNameEditFocused, setIsMenuNameEditFocused ] = useContext(
IsMenuEditorFocused
);

const { menuName, menuId } = useNavigationEditorMenu();
const { editMenuName } = useMenuEntity( menuId );
talldan marked this conversation as resolved.
Show resolved Hide resolved
const inputRef = useRef();
const [ tmpMenuName, setTmpMenuName ] = useState( menuName );
grzim marked this conversation as resolved.
Show resolved Hide resolved
const instanceId = useInstanceId( NameEditor );
const id = `components-edit-navigation-name-editor__input-${ instanceId }`;
useEffect( () => setTmpMenuName( menuName ), [ menuName ] );
useEffect( () => {
if ( isMenuNameEditFocused ) inputRef.current.focus();
}, [ isMenuNameEditFocused ] );
return (
<>
<TextControl
ref={ inputRef }
help={ __(
'A short, descriptive name used to refer to this menu elsewhere.'
) }
label={ __( 'Name' ) }
id={ id }
talldan marked this conversation as resolved.
Show resolved Hide resolved
onBlur={ () => setIsMenuNameEditFocused( false ) }
className="components-name-editor__text-control"
grzim marked this conversation as resolved.
Show resolved Hide resolved
value={ tmpMenuName }
onChange={ ( value ) => {
setTmpMenuName( value );
editMenuName( value );
} }
aria-label={ __( 'Edit menu name' ) }
grzim marked this conversation as resolved.
Show resolved Hide resolved
/>
{ /*<div>*/ }
{ /* <input*/ }
{ /* ref={ inputRef }*/ }
{ /* id={ id }*/ }
{ /* onBlur={ () => setIsMenuNameEditFocused( false ) }*/ }
{ /* className="components-text-control__input"*/ }
{ /* value={ tmpMenuName }*/ }
{ /* onChange={ ( { target: { value } } ) => {*/ }
{ /* setTmpMenuName( value );*/ }
{ /* editMenuName( value );*/ }
{ /* } }*/ }
{ /* aria-label={ __( 'Edit menu name' ) }*/ }
{ /* />*/ }
{ /*</div>*/ }
grzim marked this conversation as resolved.
Show resolved Hide resolved
</>
);
}
11 changes: 11 additions & 0 deletions packages/edit-navigation/src/components/name-editor/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.edit-navigation-name-editor__edit-name-description {
font-size: $helptext-font-size;
font-style: normal;
color: $gray-700;
}

grzim marked this conversation as resolved.
Show resolved Hide resolved
.components-name-editor__text-control {
grzim marked this conversation as resolved.
Show resolved Hide resolved
.components-base-control {
border: none;
}
}
31 changes: 31 additions & 0 deletions packages/edit-navigation/src/filters/add-menu-name-editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
import { addFilter } from '@wordpress/hooks';
import { createHigherOrderComponent } from '@wordpress/compose';
import NameDisplay from '../components/name-display';

const addMenuNameEditor = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
if ( props.name !== 'core/navigation' ) {
return <BlockEdit { ...props } />;
}
return (
<>
<BlockEdit { ...props } />
<NameDisplay />
</>
);
},
'withMenuName'
);

export default () =>
addFilter(
'editor.BlockEdit',
'core/edit-navigation/with-menu-name',
addMenuNameEditor
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
/**
* External dependencies
*/
import { set } from 'lodash';

function disableInsertingNonNavigationBlocks( settings, name ) {
if ( ! [ 'core/navigation', 'core/navigation-link' ].includes( name ) ) {
set( settings, [ 'supports', 'inserter' ], false );
}
return settings;
}

export default () =>
addFilter(
'blocks.registerBlockType',
'core/edit-navigation/disable-inserting-non-navigation-blocks',
disableInsertingNonNavigationBlocks
);
18 changes: 18 additions & 0 deletions packages/edit-navigation/src/filters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Internal dependencies
*/
import addMenuNameEditor from './add-menu-name-editor';
import disableInsertingNonNavigationBlocks from './disable-inserting-non-navigation-blocks';
import removeEditUnsupportedFeatures from './remove-edit-unsupported-features';
import removeSettingsUnsupportedFeatures from './remove-settings-unsupported-features';

export const addFilters = (
shouldAddDisableInsertingNonNavigationBlocksFilter
) => {
addMenuNameEditor();
if ( shouldAddDisableInsertingNonNavigationBlocksFilter ) {
disableInsertingNonNavigationBlocks();
}
removeEditUnsupportedFeatures();
removeSettingsUnsupportedFeatures();
};
Loading