diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 12f9b16de20037..f1678d32e1d94a 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -8,6 +8,7 @@ $z-layers: ( ".block-editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 10, ".block-editor-block-list__layout .reusable-block-indicator": 1, + ".block-editor-block-list__zoom_out": 7, // On top of insertion point. ".block-editor-block-list__breadcrumb": 2, ".components-form-toggle__input": 1, ".components-panel__header.edit-post-sidebar__panel-tabs": -1, diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 1b42d76df3e3b4..ec66a165a6a286 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -568,6 +568,18 @@ _Returns_ - `?string`: Block Template Lock +# **getZoomedBlockClientIds** + +Returns the zoomed block client IDs, if any. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `?(Array)`: Client IDs of zoomed blocks. + # **hasInserterItems** Determines whether there are items to show in the inserter. @@ -812,6 +824,14 @@ _Returns_ - `Object`: Action object. +# **clearZoomedBlocks** + +Returns an action object that zooms out from any zoomed in blocks. + +_Returns_ + +- `Object`: Action object. + # **enterFormattedText** Returns an action object used in signalling that the caret has entered formatted text. @@ -1207,4 +1227,16 @@ _Returns_ Undocumented declaration. +# **zoomBlocks** + +Returns an action object that zooms the editor into a list of specified blocks. + +_Parameters_ + +- _clientIds_ `Array`: Client IDs of the blocks to zoom into. + +_Returns_ + +- `Object`: Action object. + diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index ffb14ffca8c5eb..658b6ba1f8196f 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, first, last, every } from 'lodash'; +import { castArray, first, last, every, flatMap } from 'lodash'; /** * WordPress dependencies @@ -11,6 +11,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; import { cloneBlock, hasBlockSupport, switchToBlockType } from '@wordpress/blocks'; function BlockActions( { + onZoom, onDuplicate, onRemove, onInsertBefore, @@ -22,6 +23,7 @@ function BlockActions( { children, } ) { return children( { + onZoom, onDuplicate, onRemove, onInsertAfter, @@ -65,6 +67,7 @@ export default compose( [ } = props; const { + zoomBlocks, insertBlocks, multiSelect, removeBlocks, @@ -73,6 +76,17 @@ export default compose( [ } = dispatch( 'core/block-editor' ); return { + onZoom() { + const { getSelectedBlockClientIds, getBlockOrder } = select( + 'core/block-editor' + ); + zoomBlocks( + flatMap( getSelectedBlockClientIds(), ( clientId ) => { + const descendants = getBlockOrder( clientId ); + return descendants.length ? descendants : clientId; + } ) + ); + }, onDuplicate() { if ( isLocked || ! canDuplicate ) { return; diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 3e65a52fd3b2ec..434253031a1d40 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -12,6 +12,7 @@ import { /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { withSelect, @@ -19,6 +20,7 @@ import { __experimentalAsyncModeProvider as AsyncModeProvider, } from '@wordpress/data'; import { compose } from '@wordpress/compose'; +import { IconButton } from '@wordpress/components'; /** * Internal dependencies @@ -204,11 +206,24 @@ class BlockList extends Component { multiSelectedBlockClientIds, hasMultiSelection, renderAppender, + hasZoomedBlocks, enableAnimation, + onZoomOut, } = this.props; return (
+ { hasZoomedBlocks && ( + + { __( 'Zoom Out' ) } + + ) } + { blockClientIds.map( ( clientId ) => { const isBlockInSelection = hasMultiSelection ? multiSelectedBlockClientIds.includes( clientId ) : @@ -254,6 +269,7 @@ export default compose( [ withSelect( ( select, ownProps ) => { const { getBlockOrder, + getZoomedBlockClientIds, isSelectionEnabled, isMultiSelecting, getMultiSelectedBlocksStartClientId, @@ -265,9 +281,17 @@ export default compose( [ } = select( 'core/block-editor' ); const { rootClientId } = ownProps; + let blockClientIds = rootClientId ? + getBlockOrder( rootClientId ) : + getZoomedBlockClientIds(); + let hasZoomedBlocks = ! rootClientId; + if ( ! blockClientIds ) { + blockClientIds = getBlockOrder(); + hasZoomedBlocks = false; + } return { - blockClientIds: getBlockOrder( rootClientId ), + blockClientIds, selectionStart: getMultiSelectedBlocksStartClientId(), selectionEnd: getMultiSelectedBlocksEndClientId(), isSelectionEnabled: isSelectionEnabled(), @@ -275,6 +299,7 @@ export default compose( [ selectedBlockClientId: getSelectedBlockClientId(), multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), hasMultiSelection: hasMultiSelection(), + hasZoomedBlocks, enableAnimation: getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD, }; } ), @@ -283,12 +308,14 @@ export default compose( [ startMultiSelect, stopMultiSelect, multiSelect, + clearZoomedBlocks, } = dispatch( 'core/block-editor' ); return { onStartMultiSelect: startMultiSelect, onStopMultiSelect: stopMultiSelect, onMultiSelect: multiSelect, + onZoomOut: clearZoomedBlocks, }; } ), ] )( BlockList ); diff --git a/packages/block-editor/src/components/block-list/moving-animation.js b/packages/block-editor/src/components/block-list/moving-animation.js index 393bfa33e2a7ca..d7ffa99f60831a 100644 --- a/packages/block-editor/src/components/block-list/moving-animation.js +++ b/packages/block-editor/src/components/block-list/moving-animation.js @@ -39,9 +39,10 @@ function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationO } ref.current.style.transform = 'none'; const destination = ref.current.getBoundingClientRect(); + const defaultPosition = isSelected ? -50 : 0; const newTransform = { - x: previous ? previous.left - destination.left : 0, - y: previous ? previous.top - destination.top : 0, + x: previous ? previous.left - destination.left : defaultPosition, + y: previous ? previous.top - destination.top : defaultPosition, }; ref.current.style.transform = newTransform.x === 0 && newTransform.y === 0 ? undefined : diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 8ee3558ddf6398..d996a81c697593 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -7,6 +7,16 @@ } } +.block-editor-block-list__zoom-out { + background: $white; + border-radius: 0; + float: right; + margin-top: -50px; + position: sticky; + top: 0; + z-index: z-index(".block-editor-block-list__zoom_out"); +} + .block-editor-block-list__layout .block-editor-block-list__block.is-selected { // Needs specificity to override inherited styles. // While block is being dragged, dim the slot dragged from, and hide some UI. &.is-dragging { diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 005be38e380b43..a75be49ba2c946 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -32,7 +32,7 @@ export function BlockSettingsMenu( { clientIds } ) { return ( - { ( { onDuplicate, onRemove, onInsertAfter, onInsertBefore, canDuplicate, isLocked } ) => ( + { ( { onZoom, onDuplicate, onRemove, onInsertAfter, onInsertBefore, canDuplicate, isLocked } ) => ( ( <> + + { __( 'Zoom' ) } + <__experimentalBlockSettingsMenuFirstItem.Slot fillProps={ { onClose } } /> diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 4dafaa291fcf31..6a2c1e74857c98 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -545,6 +545,31 @@ export function replaceInnerBlocks( rootClientId, blocks, updateSelection = true }; } +/** + * Returns an action object that zooms the editor into a list of specified blocks. + * + * @param {string[]} clientIds Client IDs of the blocks to zoom into. + * + * @return {Object} Action object. + */ +export function zoomBlocks( clientIds ) { + return { + type: 'ZOOM_BLOCKS', + clientIds, + }; +} + +/** + * Returns an action object that zooms out from any zoomed in blocks. + * + * @return {Object} Action object. + */ +export function clearZoomedBlocks() { + return { + type: 'CLEAR_ZOOMED_BLOCKS', + }; +} + /** * Returns an action object used to toggle the block editing mode between * visual and HTML modes. diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 97a366e8884d2f..26e66f6a096514 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1043,6 +1043,25 @@ export function blockSelection( state = BLOCK_SELECTION_INITIAL_STATE, action ) return state; } +/** + * Reducer returning the block zoom's state. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function blockZoom( state, action ) { + switch ( action.type ) { + case 'ZOOM_BLOCKS': + return action.clientIds; + case 'CLEAR_ZOOMED_BLOCKS': + return null; + } + + return state; +} + export function blocksMode( state = {}, action ) { if ( action.type === 'TOGGLE_BLOCK_MODE' ) { const { clientId } = action; @@ -1228,6 +1247,7 @@ export default combineReducers( { isTyping, isCaretWithinFormattedText, blockSelection, + blockZoom, blocksMode, blockListSettings, insertionPoint, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 5e8771ff94935b..2224a292fca594 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -889,6 +889,17 @@ export function isSelectionEnabled( state ) { return state.blockSelection.isEnabled; } +/** + * Returns the zoomed block client IDs, if any. + * + * @param {Object} state Editor state. + * + * @return {?(string[])} Client IDs of zoomed blocks. + */ +export function getZoomedBlockClientIds( state ) { + return state.blockZoom; +} + /** * Returns the block's editing mode, defaulting to "visual" if not explicitly * assigned. @@ -992,6 +1003,10 @@ export function getTemplate( state ) { * @return {?string} Block Template Lock */ export function getTemplateLock( state, rootClientId ) { + if ( getZoomedBlockClientIds( state ) ) { + return 'all'; + } + if ( ! rootClientId ) { return state.settings.templateLock; } diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 8ea2c9a408de84..3f6dea91387324 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -709,7 +709,8 @@ export function redo() { * * @return {Object} Action object. */ -export function undo() { +export function* undo() { + yield dispatch( 'core/block-editor', 'clearZoomedBlocks' ); return { type: 'UNDO' }; }