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

Widget editor: Add toolbar button to move between widget areas #30826

Merged
merged 14 commits into from
Apr 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -17,10 +17,21 @@ import { getBlockDOMNode } from '../../utils/dom';
import { store as blockEditorStore } from '../../store';

export function useScrollSelectionIntoView( ref ) {
const selectionEnd = useSelect(
( select ) => select( blockEditorStore ).getBlockSelectionEnd(),
[]
);
// Although selectionRootClientId isn't used directly in calculating
// whether scrolling should occur, it is used as a dependency of the
// effect to take into account situations where a block might be moved
// to a different parent. In this situation, the selectionEnd clientId
// remains the same, so the rootClientId is used to trigger the effect.
const { selectionRootClientId, selectionEnd } = useSelect( ( select ) => {
const selectors = select( blockEditorStore );
const selectionEndClientId = selectors.getBlockSelectionEnd();
return {
selectionEnd: selectionEndClientId,
selectionRootClientId: selectors.getBlockRootClientId(
selectionEndClientId
),
};
}, [] );

useEffect( () => {
if ( ! selectionEnd ) {
Expand All @@ -45,7 +56,7 @@ export function useScrollSelectionIntoView( ref ) {
scrollIntoView( extentNode, scrollContainer, {
onlyScrollIfNeeded: true,
} );
}, [ selectionEnd ] );
}, [ selectionRootClientId, selectionEnd ] );
}

/**
Expand Down
54 changes: 52 additions & 2 deletions packages/e2e-tests/specs/widgets/adding-widgets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
* WordPress dependencies
*/
import {
visitAdminPage,
deactivatePlugin,
activatePlugin,
activateTheme,
clickBlockToolbarButton,
deactivatePlugin,
pressKeyWithModifier,
showBlockToolbar,
visitAdminPage,
} from '@wordpress/e2e-test-utils';

/**
Expand Down Expand Up @@ -521,6 +523,54 @@ describe( 'Widgets screen', () => {
}
` );
} );

it( 'allows widgets to be moved between widget areas using the dropdown in the block toolbar', async () => {
const widgetAreas = await page.$$(
'[aria-label="Block: Widget Area"][role="group"]'
);
const [ firstWidgetArea ] = widgetAreas;

// Insert a paragraph it should be in the first widget area.
const inserterParagraphBlock = await getBlockInGlobalInserter(
'Paragraph'
);
await inserterParagraphBlock.hover();
await inserterParagraphBlock.click();
const addedParagraphBlockInFirstWidgetArea = await firstWidgetArea.$(
'[data-block][data-type="core/paragraph"][aria-label^="Empty block"]'
);
await addedParagraphBlockInFirstWidgetArea.focus();
await page.keyboard.type( 'First Paragraph' );

// Check that the block exists in the first widget area.
await page.waitForXPath(
'//*[@aria-label="Block: Widget Area"][@role="group"][1]//p[@data-type="core/paragraph"][.="First Paragraph"]'
);

// Move the block to the second widget area.
await showBlockToolbar();
await clickBlockToolbarButton( 'Move to widget area' );
const widgetAreaButton = await page.waitForXPath(
'//button[@role="menuitemradio"][contains(.,"Footer #2")]'
);
await widgetAreaButton.click();

// Check that the block exists in the second widget area.
await page.waitForXPath(
'//*[@aria-label="Block: Widget Area"][@role="group"][2]//p[@data-type="core/paragraph"][.="First Paragraph"]'
);

// Assert that the serialized widget areas shows the block as in the second widget area.
await saveWidgets();
const serializedWidgetAreas2 = await getSerializedWidgetAreas();
expect( serializedWidgetAreas2 ).toMatchInlineSnapshot( `
Object {
"sidebar-2": "<div class=\\"widget widget_block widget_text\\"><div class=\\"widget-content\\">
<p>First Paragraph</p>
</div></div>",
}
` );
} );
} );

async function saveWidgets() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* WordPress dependencies
*/
import {
DropdownMenu,
MenuGroup,
MenuItemsChoice,
ToolbarGroup,
ToolbarItem,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { moveTo } from '@wordpress/icons';

export default function MoveToWidgetArea( {
currentWidgetArea,
widgetAreas,
onSelect,
} ) {
return (
<ToolbarGroup>
<ToolbarItem>
{ ( toggleProps ) => (
<DropdownMenu
icon={ moveTo }
label={ __( 'Move to widget area' ) }
toggleProps={ toggleProps }
>
{ ( { onClose } ) => (
<MenuGroup label={ __( 'Move to' ) }>
<MenuItemsChoice
choices={ widgetAreas.map(
( widgetArea ) => ( {
value: widgetArea.id,
label: widgetArea.name,
info: widgetArea.description,
} )
) }
value={ currentWidgetArea?.id }
onSelect={ ( value ) => {
onSelect( value );
onClose();
} }
/>
</MenuGroup>
) }
</DropdownMenu>
) }
</ToolbarItem>
</ToolbarGroup>
);
}
5 changes: 5 additions & 0 deletions packages/edit-widgets/src/filters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Internal dependencies
*/
import './move-to-widget-area';
import './replace-media-upload';
63 changes: 63 additions & 0 deletions packages/edit-widgets/src/filters/move-to-widget-area.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* WordPress dependencies
*/

import { BlockControls } from '@wordpress/block-editor';
import { createHigherOrderComponent } from '@wordpress/compose';
import { useDispatch, useSelect } from '@wordpress/data';
import { addFilter } from '@wordpress/hooks';

/**
* Internal dependencies
*/
import MoveToWidgetArea from '../components/move-to-widget-area';
import { store as editWidgetsStore } from '../store';

const withMoveToWidgetAreaToolbarItem = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const { __internalWidgetId } = props.attributes;
const { widgetAreas, currentWidgetArea } = useSelect(
( select ) => {
const selectors = select( editWidgetsStore );
return {
widgetAreas: selectors.getWidgetAreas(),
currentWidgetArea: __internalWidgetId
? selectors.getWidgetAreaForWidgetId(
__internalWidgetId
)
: undefined,
};
},
[ __internalWidgetId ]
);

const { moveBlockToWidgetArea } = useDispatch( editWidgetsStore );

return (
<>
<BlockEdit { ...props } />
{ props.name !== 'core/widget-area' && (
<BlockControls>
<MoveToWidgetArea
widgetAreas={ widgetAreas }
currentWidgetArea={ currentWidgetArea }
onSelect={ ( widgetAreaId ) => {
moveBlockToWidgetArea(
props.clientId,
widgetAreaId
);
} }
/>
</BlockControls>
) }
</>
);
},
'withMoveToWidgetAreaToolbarItem'
);

addFilter(
'editor.BlockEdit',
'core/edit-widgets/block-edit',
withMoveToWidgetAreaToolbarItem
);
4 changes: 0 additions & 4 deletions packages/edit-widgets/src/hooks/index.js

This file was deleted.

2 changes: 1 addition & 1 deletion packages/edit-widgets/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wor
* Internal dependencies
*/
import './store';
import './hooks';
import './filters';
import * as widgetArea from './blocks/widget-area';
import Layout from './components/layout';
import registerLegacyWidgetVariations from './register-legacy-widget-variations';
Expand Down
57 changes: 57 additions & 0 deletions packages/edit-widgets/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,60 @@ export function* closeGeneralSidebar() {
editWidgetsStoreName
);
}

/**
* Action that handles moving a block between widget areas
*
* @param {string} clientId The clientId of the block to move.
* @param {string} widgetAreaId The id of the widget area to move the block to.
*/
export function* moveBlockToWidgetArea( clientId, widgetAreaId ) {
const sourceRootClientId = yield select(
'core/block-editor',
'getBlockRootClientId',
[ clientId ]
);

// Search the top level blocks (widget areas) for the one with the matching
// id attribute. Makes the assumption that all top-level blocks are widget
// areas.
const widgetAreas = yield select( 'core/block-editor', 'getBlocks' );
const destinationWidgetAreaBlock = widgetAreas.find(
( { attributes } ) => attributes.id === widgetAreaId
);
const destinationRootClientId = destinationWidgetAreaBlock.clientId;

// Get the index for moving to the end of the the destination widget area.
const destinationInnerBlocksClientIds = yield select(
'core/block-editor',
'getBlockOrder',
destinationRootClientId
);
const destinationIndex = destinationInnerBlocksClientIds.length;

// Reveal the widget area, if it's not open.
const isDestinationWidgetAreaOpen = yield select(
editWidgetsStoreName,
'getIsWidgetAreaOpen',
destinationRootClientId
);

if ( ! isDestinationWidgetAreaOpen ) {
yield dispatch(
editWidgetsStoreName,
'setIsWidgetAreaOpen',
destinationRootClientId,
true
);
}

// Move the block.
yield dispatch(
'core/block-editor',
'moveBlocksToPosition',
[ clientId ],
sourceRootClientId,
destinationRootClientId,
destinationIndex
);
}
1 change: 1 addition & 0 deletions packages/icons/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export { default as more } from './library/more';
export { default as moreHorizontal } from './library/more-horizontal';
export { default as moreHorizontalMobile } from './library/more-horizontal-mobile';
export { default as moreVertical } from './library/more-vertical';
export { default as moveTo } from './library/move-to';
export { default as navigation } from './library/navigation';
export { default as overlayText } from './library/overlay-text';
export { default as pageBreak } from './library/page-break';
Expand Down
12 changes: 12 additions & 0 deletions packages/icons/src/library/move-to.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* WordPress dependencies
*/
import { Path, SVG } from '@wordpress/primitives';

const moveTo = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Path d="M19.75 9c0-1.257-.565-2.197-1.39-2.858-.797-.64-1.827-1.017-2.815-1.247-1.802-.42-3.703-.403-4.383-.396L11 4.5V6l.177-.001c.696-.006 2.416-.02 4.028.356.887.207 1.67.518 2.216.957.52.416.829.945.829 1.688 0 .592-.167.966-.407 1.23-.255.281-.656.508-1.236.674-1.19.34-2.82.346-4.607.346h-.077c-1.692 0-3.527 0-4.942.404-.732.209-1.424.545-1.935 1.108-.526.579-.796 1.33-.796 2.238 0 1.257.565 2.197 1.39 2.858.797.64 1.827 1.017 2.815 1.247 1.802.42 3.703.403 4.383.396L13 19.5h.714V22L18 18.5 13.714 15v3H13l-.177.001c-.696.006-2.416.02-4.028-.356-.887-.207-1.67-.518-2.216-.957-.52-.416-.829-.945-.829-1.688 0-.592.167-.966.407-1.23.255-.281.656-.508 1.237-.674 1.189-.34 2.819-.346 4.606-.346h.077c1.692 0 3.527 0 4.941-.404.732-.209 1.425-.545 1.936-1.108.526-.579.796-1.33.796-2.238z" />
</SVG>
);

export default moveTo;