Skip to content
This repository has been archived by the owner on Feb 18, 2021. It is now read-only.

Commit

Permalink
feat(sidebar): make items sortable by drag n drop
Browse files Browse the repository at this point in the history
  • Loading branch information
kontrollanten committed Mar 4, 2019
1 parent b396ec5 commit 3913c32
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 57 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@
},
"dependencies": {
"babel-plugin-transform-class-properties": "^6.24.1",
"dragula": "^3.7.2",
"electron-context-menu": "^0.11.0",
"electron-context-menu-handler": "^1.0.3",
"electron-debug": "^2.0.0",
Expand All @@ -135,6 +134,7 @@
"history": "^4.7.2",
"minimist": "^1.2.0",
"react": "^16.4",
"react-beautiful-dnd": "10.0.4",
"react-dom": "^16.4",
"react-hot-loader": "^4.0.0",
"react-redux": "^5.0.7",
Expand Down
43 changes: 30 additions & 13 deletions src/renderer/components/Sidebar/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

import SidebarItem from '../SidebarItem';
import styles from './style.scss';

const Sidebar = ({
accounts,
isHidden,
onChangePosition,
onRemoveAccount,
location,
}) =>
Expand All @@ -18,20 +20,34 @@ const Sidebar = ({
</div>
</Link>

{accounts
.map(account => ({ ...account, path: `/mailbox/${account.username}` }))
.map(account => ({ ...account, isActive: account.path === location.pathname }))
.map(({ username, unreadEmails, path, isActive }, index) => (
<SidebarItem
key={index}
href={path}
isActive={isActive}
onRemoveAccount={onRemoveAccount}
unreadEmails={unreadEmails}
username={username}
/>
))}
<DragDropContext onDragEnd={({ destination, source }) => onChangePosition({ from: source.index, to: destination.index })}>
<Droppable droppableId="mail-boxes">
{(provided) => (
<div ref={provided.innerRef}>
{accounts
.map(account => ({ ...account, path: `/mailbox/${account.username}` }))
.map(account => ({ ...account, isActive: account.path === location.pathname }))
.map(({ username, unreadEmails, path, isActive }, index) => (
<Draggable key={username} draggableId={username} index={index}>
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<SidebarItem
key={username}
href={path}
isActive={isActive}
onRemoveAccount={onRemoveAccount}
unreadEmails={unreadEmails}
username={username}
/>
</div>
)}
</Draggable>
))}

</div>
)}
</Droppable>
</DragDropContext>
<Link
to="/settings"
className={[styles.SettingsTab]}
Expand All @@ -43,6 +59,7 @@ const Sidebar = ({
Sidebar.propTypes = {
accounts: PropTypes.array.isRequired,
isHidden: PropTypes.bool.isRequired,
onChangePosition: PropTypes.func.isRequired,
onRemoveAccount: PropTypes.func.isRequired,
location: PropTypes.object.isRequired,
};
Expand Down
22 changes: 21 additions & 1 deletion src/renderer/components/Sidebar/index.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { shallow } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';

import Sidebar from './';

Expand All @@ -9,8 +11,10 @@ describe('components/Sidebar', () => {
accounts: [],
isHidden: false,
location: {},
onChangePosition: () => null,
onRemoveAccount: () => null
};

it('should display an accurate add account button', () => {
const context = shallow(<Sidebar {...defaultProps} />);

Expand All @@ -25,17 +29,33 @@ describe('components/Sidebar', () => {
];
const onRemoveAccount = () => null;
const context = shallow(<Sidebar {...defaultProps} accounts={accounts} onRemoveAccount={onRemoveAccount} />);
const listWrapper = shallow(context.find(Droppable).props().children({}));

accounts
.forEach(({ username }, index) => {
const node = context.find(`[href="/mailbox/${username}"]`);
const itemWrapper = shallow(listWrapper.find({ draggableId: username }).props().children({}));
const node = itemWrapper.find(`[href="/mailbox/${username}"]`);

expect(node.prop('username')).to.equal(accounts[index].username);
expect(node.prop('unreadEmails')).to.equal(accounts[index].unreadEmails);
expect(node.prop('onRemoveAccount')).to.equal(onRemoveAccount);
});
});

it('should fire onChangePosition upon DragDropContext onDragEnd', () => {
const onChangePosition = sinon.spy();
const wrapper = shallow(<Sidebar {...defaultProps} onChangePosition={onChangePosition} />);

const from = 4;
const to = 12;
wrapper.find(DragDropContext).simulate('dragEnd', {
destination: { index: to },
source: { index: from }
});

expect(onChangePosition).to.have.been.calledWith({ from, to });
});

it('should display a settings button', () => {
const context = shallow(<Sidebar {...defaultProps} />);

Expand Down
13 changes: 12 additions & 1 deletion src/renderer/containers/App/reducer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RELOAD_WEBVIEW, WEBVIEW_ERROR } from '../../middlewares/Webviews/types';
import { ADD_ACCOUNT, REMOVE_ACCOUNT, TOGGLE_SIDEBAR, UPDATE_SETTINGS, UPDATE_UNREAD_EMAILS } from './types';
import { ADD_ACCOUNT, REMOVE_ACCOUNT, TOGGLE_SIDEBAR, TOGGLE_SIDEBAR_ITEM_POSITION, UPDATE_SETTINGS, UPDATE_UNREAD_EMAILS } from './types';

const initialState = {
accounts: [],
Expand Down Expand Up @@ -34,6 +34,17 @@ export default (state = initialState, action) => {
hideSidebar: !state.settings.hideSidebar,
}
};

case TOGGLE_SIDEBAR_ITEM_POSITION: {
const accounts = [...state.accounts];
accounts.splice(action.to, 0, ...accounts.splice(action.from, 1));

return {
...state,
accounts
};
}

case UPDATE_SETTINGS:
return {
...state,
Expand Down
18 changes: 17 additions & 1 deletion src/renderer/containers/App/reducer.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from 'chai';

import { RELOAD_WEBVIEW, WEBVIEW_ERROR } from '../../middlewares/Webviews/types';
import { ADD_ACCOUNT, REMOVE_ACCOUNT, TOGGLE_SIDEBAR, UPDATE_SETTINGS, UPDATE_UNREAD_EMAILS } from './types';
import { ADD_ACCOUNT, REMOVE_ACCOUNT, TOGGLE_SIDEBAR, TOGGLE_SIDEBAR_ITEM_POSITION, UPDATE_SETTINGS, UPDATE_UNREAD_EMAILS } from './types';
import App from './reducer';

describe('containers/App/reducer', () => {
Expand Down Expand Up @@ -34,6 +34,22 @@ describe('containers/App/reducer', () => {
expect(App(initialState, { type: TOGGLE_SIDEBAR }).settings.hideSidebar).to.equal(false);
});

it('should toggle account positions upon TOGGLE_SIDEBAR_ITEM_POSITION', () => {
const accounts = [
'a',
'b',
'c',
];

expect(App({ accounts }, { type: TOGGLE_SIDEBAR_ITEM_POSITION, from: 0, to: 2 })).to.eql({
accounts: [
'b',
'c',
'a',
],
});
});

it('should provide default settings as initial state', () => {
expect(App(undefined, {}).settings).to.eql({
darkTheme: false,
Expand Down
1 change: 1 addition & 0 deletions src/renderer/containers/App/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const ADD_ACCOUNT = 'APP/ADD_ACCOUNT';
export const REMOVE_ACCOUNT = 'APP/REMOVE_ACCOUNT';

export const TOGGLE_SIDEBAR = 'APP/TOGGLE_SIDEBAR';
export const TOGGLE_SIDEBAR_ITEM_POSITION = 'APP/TOGGLE_SIDEBAR_ITEM_POSITION';

export const UPDATE_SETTINGS = 'APP/UPDATE_SETTINGS';

Expand Down
7 changes: 6 additions & 1 deletion src/renderer/containers/Sidebar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { REMOVE_ACCOUNT } from './App/types';
import { TOGGLE_SIDEBAR_ITEM_POSITION, REMOVE_ACCOUNT } from './App/types';
import Sidebar from '../components/Sidebar';

const mapStateToProps = state => {
Expand All @@ -10,6 +10,11 @@ const mapStateToProps = state => {
};

const mapDispatchToProps = dispatch => ({
onChangePosition: ({ from, to }) => dispatch({
type: TOGGLE_SIDEBAR_ITEM_POSITION,
from,
to
}),
onRemoveAccount: username => dispatch({
type: REMOVE_ACCOUNT,
username,
Expand Down
43 changes: 43 additions & 0 deletions src/renderer/containers/Sidebar.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import { expect } from 'chai';

import { TOGGLE_SIDEBAR_ITEM_POSITION } from './App/types';
import SidebarContainer from './Sidebar';

describe('containers/Sidebar', () => {
const defaultProps = {
location: {}
};
let store;

beforeEach(() => {
store = {
dispatch: sinon.spy(),
getState: () => ({
accounts: [],
settings: {
hideSidebar: false
}
}),
subscribe: () => null
};
});

it('should dispatch TOGGLE_SIDEBAR_ITEM_POSITION upon onChangePosition', () => {
const wrapper = shallow(<SidebarContainer {...defaultProps} />, {
context: { store }
});

const from = 12;
const to = 15;
wrapper.props().onChangePosition({ from, to });

expect(store.dispatch).to.have.been.calledWith({
type: TOGGLE_SIDEBAR_ITEM_POSITION,
from,
to
});
});
});
Loading

0 comments on commit 3913c32

Please sign in to comment.