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

Add multi select inspector #1811

Closed
wants to merge 1 commit into from
Closed
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
WIP
  • Loading branch information
ellatrix committed Oct 6, 2017
commit 0769a4ff567aed320d936e191f6c9ffd91312710
16 changes: 16 additions & 0 deletions blocks/library/paragraph/index.js
Original file line number Diff line number Diff line change
@@ -94,6 +94,22 @@ registerBlockType( 'core/paragraph', {
}
},

multiSelectedControls( { attributes, setAttributes } ) {
const value = attributes.reduce( ( acc, { align } ) => {
return acc === align ? acc : null;
}, attributes[ 0 ].align );

return [
<AlignmentToolbar
key="alignment-toolbar"
value={ value }
onChange={ ( nextAlign ) => {
setAttributes( { align: nextAlign } );
} }
/>,
];
},

edit( { attributes, setAttributes, insertBlocksAfter, focus, setFocus, mergeBlocks, onReplace } ) {
const { align, content, dropCap, placeholder, fontSize, backgroundColor, textColor, width } = attributes;
const toggleDropCap = () => setAttributes( { dropCap: ! dropCap } );
6 changes: 4 additions & 2 deletions editor/block-settings-menu/index.js
Original file line number Diff line number Diff line change
@@ -51,11 +51,13 @@ export default connect(
onDelete() {
dispatch( {
type: 'REMOVE_BLOCKS',
uids: [ ownProps.uid ],
uids: ownProps.uids,
} );
},
onSelect() {
dispatch( selectBlock( ownProps.uid ) );
if ( ownProps.uids.length === 1 ) {
dispatch( selectBlock( ownProps.uids[ 0 ] ) );
}
},
setActivePanel() {
dispatch( {
34 changes: 24 additions & 10 deletions editor/block-switcher/index.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
* External dependencies
*/
import { connect } from 'react-redux';
import { uniq, get, reduce, find } from 'lodash';
import { uniq, get, reduce, find, first, last } from 'lodash';
import clickOutside from 'react-click-outside';

/**
@@ -17,8 +17,8 @@ import { getBlockType, getBlockTypes, switchToBlockType } from '@wordpress/block
* Internal dependencies
*/
import './style.scss';
import { replaceBlocks } from '../actions';
import { getBlock } from '../selectors';
import { replaceBlocks, multiSelect } from '../actions';
import { getBlocksByUid } from '../selectors';

class BlockSwitcher extends Component {
constructor() {
@@ -48,15 +48,23 @@ class BlockSwitcher extends Component {
this.setState( {
open: false,
} );
this.props.onTransform( this.props.block, name );
this.props.onTransform( this.props.blocks, name );
};
}

render() {
const blockType = getBlockType( this.props.block.name );
const names = uniq( this.props.blocks.map( ( block ) => block.name ) );

// Blocks do not share the same name.
if ( names.length !== 1 ) {
return null;
}

const blockName = first( names );
const blockType = getBlockType( blockName );
const blocksToBeTransformedFrom = reduce( getBlockTypes(), ( memo, block ) => {
const transformFrom = get( block, 'transforms.from', [] );
const transformation = find( transformFrom, t => t.type === 'block' && t.blocks.indexOf( this.props.block.name ) !== -1 );
const transformation = find( transformFrom, t => t.type === 'block' && t.blocks.indexOf( blockName ) !== -1 );
return transformation ? memo.concat( [ block.name ] ) : memo;
}, [] );
const blocksToBeTransformedTo = get( blockType, 'transforms.to', [] )
@@ -115,14 +123,20 @@ class BlockSwitcher extends Component {

export default connect(
( state, ownProps ) => ( {
block: getBlock( state, ownProps.uid ),
blocks: getBlocksByUid( state, ownProps.uids ),
} ),
( dispatch, ownProps ) => ( {
onTransform( block, name ) {
onTransform( blocks, name ) {
dispatch( replaceBlocks(
[ ownProps.uid ],
switchToBlockType( block, name )
ownProps.uids,
blocks.reduce( ( acc, block ) => {
return [ ...acc, ...switchToBlockType( block, name ) ];
}, [] ),
) );

if ( blocks.length > 1 ) {
dispatch( multiSelect( first( blocks ).uid, last( blocks ).uid ) );
}
},
} )
)( clickOutside( BlockSwitcher ) );
8 changes: 4 additions & 4 deletions editor/block-toolbar/index.js
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ class BlockToolbar extends Component {

render() {
const { showMobileControls } = this.state;
const { uid } = this.props;
const { uids } = this.props;

const toolbarClassname = classnames( 'editor-block-toolbar', {
'is-showing-mobile-controls': showMobileControls,
@@ -60,7 +60,7 @@ class BlockToolbar extends Component {
<div className={ toolbarClassname }>
<div className="editor-block-toolbar__group">
{ ! showMobileControls && [
<BlockSwitcher key="switcher" uid={ uid } />,
<BlockSwitcher key="switcher" uids={ uids } />,
<Slot key="slot" name="Formatting.Toolbar" />,
] }
<Toolbar className="editor-block-toolbar__mobile-tools">
@@ -74,8 +74,8 @@ class BlockToolbar extends Component {

{ showMobileControls &&
<div className="editor-block-toolbar__mobile-tools-content">
<BlockMover uids={ [ uid ] } />
<BlockRightMenu uid={ uid } />
<BlockMover uids={ uids } />
<BlockRightMenu uids={ uids } />
</div>
}
</Toolbar>
47 changes: 2 additions & 45 deletions editor/header/index.js
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import { connect } from 'react-redux';
/**
* WordPress dependencies
*/
import { sprintf, _n, __ } from '@wordpress/i18n';
import { __ } from '@wordpress/i18n';
import { IconButton } from '@wordpress/components';

/**
@@ -18,53 +18,16 @@ import PublishButton from './publish-button';
import PreviewButton from './preview-button';
import ModeSwitcher from './mode-switcher';
import Inserter from '../inserter';
import { getMultiSelectedBlockUids, hasEditorUndo, hasEditorRedo, isEditorSidebarOpened } from '../selectors';
import { clearSelectedBlock } from '../actions';
import { hasEditorUndo, hasEditorRedo, isEditorSidebarOpened } from '../selectors';

function Header( {
multiSelectedBlockUids,
onRemove,
onDeselect,
undo,
redo,
hasRedo,
hasUndo,
toggleSidebar,
isSidebarOpened,
} ) {
const count = multiSelectedBlockUids.length;

if ( count ) {
return (
<div
role="region"
aria-label={ __( 'Editor toolbar' ) }
className="editor-header editor-header-multi-select"
>
<div className="editor-selected-count">
{ sprintf( _n( '%d block selected', '%d blocks selected', count ), count ) }
</div>
<div className="editor-selected-delete">
<IconButton
icon="trash"
label={ __( 'Delete selected blocks' ) }
onClick={ () => onRemove( multiSelectedBlockUids ) }
focus={ true }
>
{ __( 'Delete' ) }
</IconButton>
</div>
<div className="editor-selected-clear">
<IconButton
icon="no"
label={ __( 'Clear selected blocks' ) }
onClick={ () => onDeselect() }
/>
</div>
</div>
);
}

return (
<div
role="region"
@@ -102,17 +65,11 @@ function Header( {

export default connect(
( state ) => ( {
multiSelectedBlockUids: getMultiSelectedBlockUids( state ),
hasUndo: hasEditorUndo( state ),
hasRedo: hasEditorRedo( state ),
isSidebarOpened: isEditorSidebarOpened( state ),
} ),
( dispatch ) => ( {
onDeselect: () => dispatch( clearSelectedBlock() ),
onRemove: ( uids ) => dispatch( {
type: 'REMOVE_BLOCKS',
uids,
} ),
undo: () => dispatch( { type: 'UNDO' } ),
redo: () => dispatch( { type: 'REDO' } ),
toggleSidebar: () => dispatch( { type: 'TOGGLE_SIDEBAR' } ),
15 changes: 0 additions & 15 deletions editor/header/style.scss
Original file line number Diff line number Diff line change
@@ -73,21 +73,6 @@
right: $admin-sidebar-width-big;
}

.editor-header-multi-select {
background: $blue-medium-100;
border-bottom: 1px solid $blue-medium-200;
}

.editor-selected-count {
padding-right: $item-spacing;
color: $dark-gray-500;
border-right: 1px solid $light-gray-500;
}

.editor-selected-clear {
margin: 0 0 0 auto;
}

// hide all action buttons except the inserter on mobile
.editor-header__content-tools > .components-button {
display: none;
11 changes: 5 additions & 6 deletions editor/modes/visual-editor/block.js
Original file line number Diff line number Diff line change
@@ -340,12 +340,11 @@ class VisualEditorBlock extends Component {
>
<BlockDropZone index={ order } />
{ ( showUI || isHovered ) && <BlockMover uids={ [ block.uid ] } /> }
{ ( showUI || isHovered ) && <BlockRightMenu uid={ block.uid } /> }
{ showUI && isValid && <BlockToolbar uid={ block.uid } /> }

{ isFirstMultiSelected && (
<BlockMover uids={ multiSelectedBlockUids } />
) }
{ ( showUI || isHovered ) && <BlockRightMenu uids={ [ block.uid ] } /> }
{ showUI && isValid && <BlockToolbar uids={ [ block.uid ] } /> }
{ isFirstMultiSelected && <BlockMover uids={ multiSelectedBlockUids } /> }
{ isFirstMultiSelected && <BlockRightMenu uids={ multiSelectedBlockUids } /> }
{ isFirstMultiSelected && isValid && <BlockToolbar uids={ multiSelectedBlockUids } /> }
<div
onKeyPress={ this.maybeStartTyping }
onDragStart={ ( event ) => event.preventDefault() }
25 changes: 25 additions & 0 deletions editor/selectors.js
Original file line number Diff line number Diff line change
@@ -440,6 +440,15 @@ export const getBlocks = createSelector(
]
);

export const getBlocksByUid = createSelector(
( state, uids ) => {
return uids.map( ( uid ) => getBlock( state, uid ) );
},
( state ) => [
state.editor.blocksByUid,
]
);

/**
* Returns the number of blocks currently present in the post.
*
@@ -450,6 +459,22 @@ export function getBlockCount( state ) {
return getBlockUids( state ).length;
}

/**
* Returns the number of blocks currently selected in the post.
*
* @param {Object} state Global application state
* @return {Object} Number of blocks selected in the post
*/
export function getSelectedBlockCount( state ) {
const multiSelectedBlockCount = getMultiSelectedBlockUids( state ).length;

if ( multiSelectedBlockCount ) {
return multiSelectedBlockCount;
}

return state.blockSelection.start ? 1 : 0;
}

/**
* Returns the currently selected block, or null if there is no selected block.
*
6 changes: 3 additions & 3 deletions editor/sidebar/header.js
Original file line number Diff line number Diff line change
@@ -6,15 +6,15 @@ import { connect } from 'react-redux';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { __, _n, sprintf } from '@wordpress/i18n';
import { IconButton } from '@wordpress/components';

/**
* Internal Dependencies
*/
import { getActivePanel } from '../selectors';

const SidebarHeader = ( { panel, onSetPanel, toggleSidebar } ) => {
const SidebarHeader = ( { panel, onSetPanel, toggleSidebar, count } ) => {
return (
<div className="components-panel__header editor-sidebar__panel-tabs">
<button
@@ -29,7 +29,7 @@ const SidebarHeader = ( { panel, onSetPanel, toggleSidebar } ) => {
className={ `editor-sidebar__panel-tab ${ panel === 'block' ? 'is-active' : '' }` }
aria-label={ __( 'Block settings' ) }
>
{ __( 'Block' ) }
{ sprintf( _n( 'Block', '%d Blocks', count ), count ) }
</button>
<IconButton
onClick={ toggleSidebar }
11 changes: 7 additions & 4 deletions editor/sidebar/index.js
Original file line number Diff line number Diff line change
@@ -15,15 +15,17 @@ import { withFocusReturn } from '@wordpress/components';
import './style.scss';
import PostSettings from './post-settings';
import BlockInspector from './block-inspector';
import MultiBlockInspector from './multi-block-inspector';
import Header from './header';
import { getActivePanel } from '../selectors';
import { getActivePanel, getSelectedBlockCount } from '../selectors';

const Sidebar = ( { panel } ) => {
const Sidebar = ( { panel, selectedBlockCount } ) => {
return (
<div className="editor-sidebar" role="region" aria-label={ __( 'Editor settings' ) }>
<Header />
<Header count={ selectedBlockCount || 1 } />
{ panel === 'document' && <PostSettings /> }
{ panel === 'block' && <BlockInspector /> }
{ panel === 'block' && selectedBlockCount === 1 && <BlockInspector /> }
{ panel === 'block' && selectedBlockCount > 1 && <MultiBlockInspector /> }
</div>
);
};
@@ -32,6 +34,7 @@ export default connect(
( state ) => {
return {
panel: getActivePanel( state ),
selectedBlockCount: getSelectedBlockCount( state ),
};
}
)( withFocusReturn( Sidebar ) );
Loading