Skip to content

Commit 413e853

Browse files
authored
Visual Editor: Adding the Block Mover (up/down arrows)(#412)
1 parent 6808250 commit 413e853

File tree

7 files changed

+233
-7
lines changed

7 files changed

+233
-7
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { connect } from 'react-redux';
5+
import classnames from 'classnames';
6+
import { first, last } from 'lodash';
7+
8+
/**
9+
* Internal dependencies
10+
*/
11+
import './style.scss';
12+
import IconButton from 'components/icon-button';
13+
14+
function BlockMover( { onMoveUp, onMoveDown, isFirst, isLast } ) {
15+
return (
16+
<div className="editor-block-mover">
17+
<IconButton
18+
className={ classnames( 'editor-block-mover__control', { 'is-disabled': isFirst } ) }
19+
onClick={ onMoveUp }
20+
icon="arrow-up-alt2"
21+
/>
22+
<IconButton
23+
className={ classnames( 'editor-block-mover__control', { 'is-disabled': isLast } ) }
24+
onClick={ onMoveDown }
25+
icon="arrow-down-alt2"
26+
/>
27+
</div>
28+
);
29+
}
30+
31+
export default connect(
32+
( state, ownProps ) => ( {
33+
isFirst: first( state.blocks.order ) === ownProps.uid,
34+
isLast: last( state.blocks.order ) === ownProps.uid
35+
} ),
36+
( dispatch, ownProps ) => ( {
37+
onMoveDown() {
38+
dispatch( {
39+
type: 'MOVE_BLOCK_DOWN',
40+
uid: ownProps.uid
41+
} );
42+
},
43+
onMoveUp() {
44+
dispatch( {
45+
type: 'MOVE_BLOCK_UP',
46+
uid: ownProps.uid
47+
} );
48+
}
49+
} )
50+
)( BlockMover );
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.editor-block-mover {
2+
position: absolute;
3+
top: 10px;
4+
left: 0;
5+
}
6+
7+
.editor-block-mover__control {
8+
display: block;
9+
padding: 0;
10+
border: none;
11+
outline: none;
12+
background: none;
13+
color: $light-gray-600;
14+
cursor: pointer;
15+
width: 20px;
16+
height: 20px;
17+
18+
&:hover {
19+
color: $dark-gray-900;
20+
}
21+
22+
&.is-disabled {
23+
color: $light-gray-100;
24+
}
25+
26+
.dashicon {
27+
display: block;
28+
}
29+
}

editor/components/click-outside/index.js

Whitespace-only changes.

editor/modes/visual-editor/block.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import classnames from 'classnames';
88
* Internal dependencies
99
*/
1010
import Toolbar from 'components/toolbar';
11+
import BlockMover from 'components/block-mover';
1112

1213
function VisualEditorBlock( props ) {
1314
const { block } = props;
@@ -62,6 +63,7 @@ function VisualEditorBlock( props ) {
6263
onMouseLeave={ onMouseLeave }
6364
className={ className }
6465
>
66+
{ ( isSelected || isHovered ) && <BlockMover uid={ block.uid } /> }
6567
{ isSelected && settings.controls ? (
6668
<Toolbar
6769
controls={ settings.controls.map( ( control ) => ( {

editor/modes/visual-editor/style.scss

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,37 @@
1717

1818
.editor-visual-editor__block {
1919
position: relative;
20-
padding: 15px;
2120
border: 2px solid transparent;
2221
transition: 0.2s border-color;
22+
margin-left: -35px;
23+
padding: 15px 15px 15px 50px;
2324

24-
&.is-hovered {
25-
border-left: 2px solid $light-gray-500;
25+
&:before {
26+
z-index: -1;
27+
content: '';
28+
position: absolute;
29+
top: 0;
30+
bottom: 0;
31+
left: 33px;
32+
right: 0;
33+
border: 2px solid transparent;
34+
transition: 0.2s border-color;
2635
}
2736

28-
&.is-selected {
29-
border: 2px solid $light-gray-500;
37+
&.is-hovered:before {
38+
border-left-color: $light-gray-500;
39+
}
40+
41+
&.is-selected:before {
42+
border-color: $light-gray-500;
3043
}
3144
}
3245

3346
.editor-visual-editor__block .editor-toolbar {
3447
position: absolute;
3548
bottom: 100%;
3649
margin-bottom: -4px;
37-
left: 8px;
50+
left: 43px;
3851
}
3952

4053
.editor-visual-editor .editor-inserter {

editor/state.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* External dependencies
33
*/
44
import { combineReducers, createStore } from 'redux';
5-
import { keyBy } from 'lodash';
5+
import { keyBy, last } from 'lodash';
66

77
/**
88
* Reducer returning editor blocks state, an combined reducer of keys byUid,
@@ -31,9 +31,37 @@ export const blocks = combineReducers( {
3131
return state;
3232
},
3333
order( state = [], action ) {
34+
let index;
35+
let swappedUid;
3436
switch ( action.type ) {
3537
case 'REPLACE_BLOCKS':
3638
return action.blockNodes.map( ( { uid } ) => uid );
39+
40+
case 'MOVE_BLOCK_UP':
41+
if ( action.uid === state[ 0 ] ) {
42+
return state;
43+
}
44+
index = state.indexOf( action.uid );
45+
swappedUid = state[ index - 1 ];
46+
return [
47+
...state.slice( 0, index - 1 ),
48+
action.uid,
49+
swappedUid,
50+
...state.slice( index + 1 )
51+
];
52+
53+
case 'MOVE_BLOCK_DOWN':
54+
if ( action.uid === last( state ) ) {
55+
return state;
56+
}
57+
index = state.indexOf( action.uid );
58+
swappedUid = state[ index + 1 ];
59+
return [
60+
...state.slice( 0, index ),
61+
swappedUid,
62+
action.uid,
63+
...state.slice( index + 2 )
64+
];
3765
}
3866

3967
return state;

editor/test/state.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,110 @@ describe( 'state', () => {
124124
expect( state.hovered.kumquat ).to.be.false();
125125
expect( state.selected.kumquat ).to.be.true();
126126
} );
127+
128+
it( 'should move the block up', () => {
129+
const original = deepFreeze( {
130+
byUid: {
131+
chicken: {
132+
uid: 'chicken',
133+
blockType: 'core/test-block',
134+
attributes: {}
135+
},
136+
ribs: {
137+
uid: 'ribs',
138+
blockType: 'core/test-block',
139+
attributes: {}
140+
}
141+
},
142+
order: [ 'chicken', 'ribs' ],
143+
selected: {},
144+
hovered: {}
145+
} );
146+
const state = blocks( original, {
147+
type: 'MOVE_BLOCK_UP',
148+
uid: 'ribs'
149+
} );
150+
151+
expect( state.order ).to.eql( [ 'ribs', 'chicken' ] );
152+
} );
153+
154+
it( 'should not move the first block up', () => {
155+
const original = deepFreeze( {
156+
byUid: {
157+
chicken: {
158+
uid: 'chicken',
159+
blockType: 'core/test-block',
160+
attributes: {}
161+
},
162+
ribs: {
163+
uid: 'ribs',
164+
blockType: 'core/test-block',
165+
attributes: {}
166+
}
167+
},
168+
order: [ 'chicken', 'ribs' ],
169+
selected: {},
170+
hovered: {}
171+
} );
172+
const state = blocks( original, {
173+
type: 'MOVE_BLOCK_UP',
174+
uid: 'chicken'
175+
} );
176+
177+
expect( state.order ).to.equal( original.order );
178+
} );
179+
180+
it( 'should move the block down', () => {
181+
const original = deepFreeze( {
182+
byUid: {
183+
chicken: {
184+
uid: 'chicken',
185+
blockType: 'core/test-block',
186+
attributes: {}
187+
},
188+
ribs: {
189+
uid: 'ribs',
190+
blockType: 'core/test-block',
191+
attributes: {}
192+
}
193+
},
194+
order: [ 'chicken', 'ribs' ],
195+
selected: {},
196+
hovered: {}
197+
} );
198+
const state = blocks( original, {
199+
type: 'MOVE_BLOCK_DOWN',
200+
uid: 'chicken'
201+
} );
202+
203+
expect( state.order ).to.eql( [ 'ribs', 'chicken' ] );
204+
} );
205+
206+
it( 'should not move the last block down', () => {
207+
const original = deepFreeze( {
208+
byUid: {
209+
chicken: {
210+
uid: 'chicken',
211+
blockType: 'core/test-block',
212+
attributes: {}
213+
},
214+
ribs: {
215+
uid: 'ribs',
216+
blockType: 'core/test-block',
217+
attributes: {}
218+
}
219+
},
220+
order: [ 'chicken', 'ribs' ],
221+
selected: {},
222+
hovered: {}
223+
} );
224+
const state = blocks( original, {
225+
type: 'MOVE_BLOCK_DOWN',
226+
uid: 'ribs'
227+
} );
228+
229+
expect( state.order ).to.equal( original.order );
230+
} );
127231
} );
128232

129233
describe( 'mode()', () => {

0 commit comments

Comments
 (0)