Skip to content

Commit

Permalink
Add ability to sort swaps in Trades view (#468)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevva authored and sindresorhus committed Aug 16, 2018
1 parent e00eff5 commit d505b8e
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 37 deletions.
7 changes: 6 additions & 1 deletion app/locales/en-US/swap.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@
"view": "View"
},
"list": {
"baseAmount": "Base amount",
"date": "Date",
"cancel": "Cancel",
"empty": "No swaps yet"
"empty": "No swaps yet",
"pair": "Pair",
"quoteAmount": "Quote amount",
"status": "Status"
},
"status": {
"completed": "Completed",
Expand Down
180 changes: 148 additions & 32 deletions app/renderer/components/SwapList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {api} from 'electron-util';
import React from 'react';
import PropTypes from 'prop-types';
import {format as formatDate} from 'date-fns';
import {AutoSizer, CellMeasurer, CellMeasurerCache, List} from 'react-virtualized';
import _ from 'lodash';
import appContainer from 'containers/App';
import tradesContainer from 'containers/Trades';
import Empty from 'components/Empty';
Expand All @@ -12,6 +14,11 @@ import './SwapList.scss';

const t = translate(['swap', 'exchange']);

const SortDirections = {
ASC: Symbol('asc'),
DESC: Symbol('desc'),
};

// eslint-disable-next-line no-unused-vars
class CancelButton extends React.Component {
state = {
Expand Down Expand Up @@ -45,8 +52,39 @@ class CancelButton extends React.Component {
}
}

const SwapItem = ({swap}) => (
<div className={`row ${swap.orderType}`}>
const SwapHeaderColumn = ({children, onClick, sortBy, sortDirection, sortKeys, ...props}) => {
const isSorting = _.isEqual(sortKeys, sortBy);

return (
<div {...props} onClick={onClick(sortKeys, isSorting)}>
{children}
{isSorting && <span className={`sort ${sortDirection === SortDirections.ASC ? 'asc' : 'desc'}`}/>}
</div>
);
};

const SwapHeader = props => (
<div className="row header">
<SwapHeaderColumn {...props} className="timestamp" sortKeys={['timeStarted']}>
{t('list.date')}
</SwapHeaderColumn>
<SwapHeaderColumn {...props} className="pairs" sortKeys={['baseCurrency', 'quoteCurrency', 'timeStarted']}>
{t('list.pair')}
</SwapHeaderColumn>
<SwapHeaderColumn {...props} className="base-amount" sortKeys={['baseCurrency', 'baseCurrencyAmount', 'timeStarted']}>
{t('list.baseAmount')}
</SwapHeaderColumn>
<SwapHeaderColumn {...props} className="quote-amount" sortKeys={['quoteCurrency', 'quoteCurrencyAmount', 'timeStarted']}>
{t('list.quoteAmount')}
</SwapHeaderColumn>
<SwapHeaderColumn {...props} className="status" sortKeys={['status', 'timeStarted']}>
{t('list.status')}
</SwapHeaderColumn>
</div>
);

const SwapItem = ({style, swap}) => (
<div className={`row ${swap.orderType}`} style={style}>
<div className="timestamp">{formatDate(swap.timeStarted, 'HH:mm DD/MM/YY')}</div>
<div className="pairs">{swap.baseCurrency}/{swap.quoteCurrency}</div>
<div className="base-amount">{swap.baseCurrencyAmount} {swap.baseCurrency}</div>
Expand All @@ -69,45 +107,123 @@ const SwapItem = ({swap}) => (
</div>
);

const SwapList = ({swaps, limit, showCancel}) => {
if (swaps.length === 0) {
return <Empty show text={t('list.empty')}/>;
}
class SwapList extends React.Component {
state = {
sortBy: this.props.sortBy,
sortDirection: this.props.sortDirection,
};

cache = new CellMeasurerCache({
fixedWidth: true,
keyMapper: () => 1, // Only measure height on first item and assume rest has the same
});

const shouldLimit = limit && limit < swaps.length;
if (shouldLimit) {
swaps = swaps.slice(0, limit);
handleResize = () => {
this.cache.clearAll();
}

return (
<div className="SwapList">
{
swaps.map(swap => (
<SwapItem
key={swap.uuid}
swap={swap}
showCancel={showCancel}
/>
))
handleSort = (sortBy, isSorting) => () => {
this.setState(state => {
if (!isSorting) {
return {
sortBy,
sortDirection: SortDirections.ASC,
};
}
{shouldLimit &&
<Link
className="view-all-swaps"
onClick={() => {
appContainer.setActiveView('Trades');
tradesContainer.setActiveView('SwapHistory');
}}
>
{t('swaps.viewAllSwaps')}
</Link>

if (state.sortDirection === SortDirections.DESC) {
return {
sortBy: null,
sortDirection: null,
};
}
</div>
);
};

return {
sortBy,
sortDirection: state.sortDirection === SortDirections.ASC ? SortDirections.DESC : SortDirections.ASC,
};
});
};

renderRow = swaps => ({index, key, parent, style}) => {
const {showCancel} = this.props;
const swap = swaps[index];

return (
<CellMeasurer key={key} cache={this.cache} parent={parent} rowIndex={index}>
<SwapItem showCancel={showCancel} style={style} swap={swap}/>
</CellMeasurer>
);
};

render() {
let {showHeader, swaps, limit} = this.props;
const {sortBy, sortDirection} = this.state;

if (swaps.length === 0) {
return <Empty show text={t('list.empty')}/>;
}

const shouldLimit = limit && limit < swaps.length;
if (shouldLimit) {
swaps = swaps.slice(0, limit);
}

if (sortBy) {
swaps = _.sortBy(swaps, sortBy);
}

if (sortDirection === SortDirections.DESC) {
swaps.reverse();
}

return (
<div className="SwapList">
{showHeader && <SwapHeader onClick={this.handleSort} sortBy={sortBy} sortDirection={sortDirection}/>}
<div className="container">
<AutoSizer onResize={this.handleResize}>
{({width, height}) => (
<List
deferredMeasurementCache={this.cache}
height={height}
rowCount={swaps.length}
rowHeight={this.cache.rowHeight}
rowRenderer={this.renderRow(swaps)}
width={width}
/>
)}
</AutoSizer>
</div>
{shouldLimit &&
<Link
className="view-all-swaps"
onClick={() => {
appContainer.setActiveView('Trades');
tradesContainer.setActiveView('SwapHistory');
}}
>
{t('swaps.viewAllSwaps')}
</Link>
}
</div>
);
}
}

SwapList.propTypes = {
showCancel: PropTypes.bool,
sortBy: PropTypes.arrayOf(PropTypes.string),
sortDirection: PropTypes.symbol,
swaps: PropTypes.arrayOf(PropTypes.object),
};

SwapList.defaultProps = {
sortBy: ['timeStarted'],
sortDirection: SortDirections.DESC,
};

export default SwapList;

export {
SortDirections,
};
52 changes: 52 additions & 0 deletions app/renderer/components/SwapList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
height: 0;
min-height: 100%;

.container {
position: relative;
flex: 1;
}

.row {
display: grid;
grid-template-areas:
Expand Down Expand Up @@ -64,6 +69,53 @@
}
}

.header {
padding-top: 0;
padding-bottom: 0;

> * {
position: relative;
padding: 10px 0;
color: var(--text-color);
transition: color 0.3s;
cursor: default;
user-select: none;

&:hover {
color: var(--swap-header-hover-color);

.sort::after {
border-color: var(--swap-header-hover-color);
}
}
}

.sort {
position: absolute;
top: calc(50% + 1px);
margin-left: 10px;
transform: rotate(45deg);
}

.sort::after {
content: '';
position: absolute;
width: 5px;
height: 5px;
margin: -3px 0 0 -3px;
border-left: none;
border-top: none;
border-right: 1px var(--text-color) solid;
border-bottom: 1px var(--text-color) solid;
transition: border-color 0.3s;
}

.asc {
top: calc(50% + 2px);
transform: rotate(225deg);
}
}

@media (min-width: 1260px) {
.row {
grid-template-areas: 'timestamp pairs base-amount quote-amount status buttons';
Expand Down
2 changes: 2 additions & 0 deletions app/renderer/styles/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
--timeseries-chart-background-color: #313b5e;
--timeseries-chart-stroke: #8050ef;
--swap-status-border-color: rgba(255, 255, 255, 0.2);
--swap-header-hover-color: hsla(216, 27%, 80%, 1);
--depth-chart-buy-background-color: #275049;
--depth-chart-buy-stroke-color: #28af60;
--depth-chart-sell-background-color: #5a2947;
Expand Down Expand Up @@ -68,6 +69,7 @@ html[data-theme='light'] {
--modal-close-button-color: hsla(216, 15%, 44%, 1);
--timeseries-chart-background-color: var(--primary-color-opacity-0-5);
--swap-status-border-color: rgba(0, 0, 0, 0.2);
--swap-header-hover-color: hsla(216, 27%, 20%, 1);
--depth-chart-buy-background-color: #e3f2ee;
--depth-chart-buy-stroke-color: #7ac89b;
--depth-chart-sell-background-color: #f6e1ed;
Expand Down
4 changes: 2 additions & 2 deletions app/renderer/views/Trades.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ const TabView = ({component}) => (
const OpenOrders = () => {
const {state} = exchangeContainer;
const filteredData = state.swapHistory.filter(x => !['completed', 'failed'].includes(x.status));
return <SwapList swaps={filteredData} showCancel/>;
return <SwapList swaps={filteredData} showCancel showHeader/>;
};

const SwapHistory = () => {
const {state} = exchangeContainer;
const filteredData = state.swapHistory.filter(x => ['completed', 'failed'].includes(x.status));
return <SwapList swaps={filteredData} showCancel/>;
return <SwapList swaps={filteredData} showCancel showHeader/>;
};

const Trades = props => (
Expand Down
11 changes: 10 additions & 1 deletion app/renderer/views/Trades.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@
font-size: 14px;
}

.header {
padding-top: 0;
padding-bottom: 0;

> * {
padding: 12px 0;
}
}

.view,
.cancel {
&__button {
Expand All @@ -62,7 +71,7 @@
}

.status {
justify-self: center;
justify-self: start;
}

.buttons {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"react-popper": "^1.0.0",
"react-select": "^1.2.1",
"react-transition-group": "^2.4.0",
"react-virtualized": "^9.20.1",
"read-blob": "^1.1.0",
"recharts": "^1.1.0",
"round-to": "^3.0.0",
Expand Down
Loading

0 comments on commit d505b8e

Please sign in to comment.