Skip to content

Commit b7df80a

Browse files
siddhant1catarak
authored andcommitted
Add sorting to sketches #789 (#910)
* reselect added * Added Reselect Sorting * Refactor App * added svgs * Refactor * Fixed Issues * re: #789, update sorting styling, create sorting actions and reducers, add sort by sketch name * re #789, change names of svg icons * re: #789, use orderBy instead of sortBy, fix styling jumps
1 parent 18f646b commit b7df80a

File tree

12 files changed

+219
-22
lines changed

12 files changed

+219
-22
lines changed

client/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ export const HIDE_RUNTIME_ERROR_WARNING = 'HIDE_RUNTIME_ERROR_WARNING';
118118
export const SHOW_RUNTIME_ERROR_WARNING = 'SHOW_RUNTIME_ERROR_WARNING';
119119
export const SET_ASSETS = 'SET_ASSETS';
120120

121+
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
122+
export const SET_SORTING = 'SET_SORTING';
123+
121124
export const START_LOADING = 'START_LOADING';
122125
export const STOP_LOADING = 'STOP_LOADING';
123126

client/images/sort-arrow-down.svg

Lines changed: 9 additions & 0 deletions
Loading

client/images/sort-arrow-up.svg

Lines changed: 9 additions & 0 deletions
Loading

client/index.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require('./images/p5js-square-logo.png');
1313

1414
const history = browserHistory;
1515
const initialState = window.__INITIAL_STATE__;
16+
1617
const store = configureStore(initialState);
1718

1819
const App = () => (

client/modules/IDE/actions/sorting.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as ActionTypes from '../../../constants';
2+
3+
export const DIRECTION = {
4+
ASC: 'ASCENDING',
5+
DESC: 'DESCENDING'
6+
};
7+
8+
export function setSorting(field, direction) {
9+
return {
10+
type: ActionTypes.SET_SORTING,
11+
payload: {
12+
field,
13+
direction
14+
}
15+
};
16+
}
17+
18+
export function resetSorting() {
19+
return setSorting('createdAt', DIRECTION.DESC);
20+
}
21+
22+
export function toggleDirectionForField(field) {
23+
return {
24+
type: ActionTypes.TOGGLE_DIRECTION,
25+
field
26+
};
27+
}

client/modules/IDE/components/SketchList.jsx

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,24 @@ import InlineSVG from 'react-inlinesvg';
66
import { connect } from 'react-redux';
77
import { browserHistory, Link } from 'react-router';
88
import { bindActionCreators } from 'redux';
9+
import classNames from 'classnames';
910
import * as ProjectActions from '../actions/project';
1011
import * as SketchActions from '../actions/projects';
1112
import * as ToastActions from '../actions/toast';
13+
import * as SortingActions from '../actions/sorting';
14+
import getSortedSketches from '../selectors/projects';
1215
import Loader from '../../App/components/loader';
1316

1417
const trashCan = require('../../../images/trash-can.svg');
18+
const arrowUp = require('../../../images/sort-arrow-up.svg');
19+
const arrowDown = require('../../../images/sort-arrow-down.svg');
1520

1621
class SketchList extends React.Component {
1722
constructor(props) {
1823
super(props);
1924
this.props.getProjects(this.props.username);
25+
this.props.resetSorting();
26+
this._renderFieldHeader = this._renderFieldHeader.bind(this);
2027
}
2128

2229
getSketchesTitle() {
@@ -30,35 +37,56 @@ class SketchList extends React.Component {
3037
return !this.props.loading && this.props.sketches.length > 0;
3138
}
3239

33-
renderLoader() {
40+
_renderLoader() {
3441
if (this.props.loading) return <Loader />;
3542
return null;
3643
}
3744

38-
renderEmptyTable() {
45+
_renderEmptyTable() {
3946
if (!this.props.loading && this.props.sketches.length === 0) {
4047
return (<p className="sketches-table__empty">No sketches.</p>);
4148
}
4249
return null;
4350
}
4451

52+
_renderFieldHeader(fieldName, displayName) {
53+
const { field, direction } = this.props.sorting;
54+
const headerClass = classNames({
55+
'sketches-table__header': true,
56+
'sketches-table__header--selected': field === fieldName
57+
});
58+
return (
59+
<th scope="col">
60+
<button className="sketch-list__sort-button" onClick={() => this.props.toggleDirectionForField(fieldName)}>
61+
<span className={headerClass}>{displayName}</span>
62+
{field === fieldName && direction === SortingActions.DIRECTION.ASC &&
63+
<InlineSVG src={arrowUp} />
64+
}
65+
{field === fieldName && direction === SortingActions.DIRECTION.DESC &&
66+
<InlineSVG src={arrowDown} />
67+
}
68+
</button>
69+
</th>
70+
);
71+
}
72+
4573
render() {
4674
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
4775
return (
4876
<div className="sketches-table-container">
4977
<Helmet>
5078
<title>{this.getSketchesTitle()}</title>
5179
</Helmet>
52-
{this.renderLoader()}
53-
{this.renderEmptyTable()}
80+
{this._renderLoader()}
81+
{this._renderEmptyTable()}
5482
{this.hasSketches() &&
5583
<table className="sketches-table" summary="table containing all saved projects">
5684
<thead>
5785
<tr>
5886
<th className="sketch-list__trash-column" scope="col"></th>
59-
<th scope="col">Sketch</th>
60-
<th scope="col">Date created</th>
61-
<th scope="col">Date updated</th>
87+
{this._renderFieldHeader('name', 'Sketch')}
88+
{this._renderFieldHeader('createdAt', 'Date Created')}
89+
{this._renderFieldHeader('updatedAt', 'Date Updated')}
6290
</tr>
6391
</thead>
6492
<tbody>
@@ -112,7 +140,13 @@ SketchList.propTypes = {
112140
})).isRequired,
113141
username: PropTypes.string,
114142
loading: PropTypes.bool.isRequired,
115-
deleteProject: PropTypes.func.isRequired
143+
deleteProject: PropTypes.func.isRequired,
144+
toggleDirectionForField: PropTypes.func.isRequired,
145+
resetSorting: PropTypes.func.isRequired,
146+
sorting: PropTypes.shape({
147+
field: PropTypes.string.isRequired,
148+
direction: PropTypes.string.isRequired
149+
}).isRequired,
116150
};
117151

118152
SketchList.defaultProps = {
@@ -122,13 +156,14 @@ SketchList.defaultProps = {
122156
function mapStateToProps(state) {
123157
return {
124158
user: state.user,
125-
sketches: state.sketches,
126-
loading: state.loading,
159+
sketches: getSortedSketches(state),
160+
sorting: state.sorting,
161+
loading: state.loading
127162
};
128163
}
129164

130165
function mapDispatchToProps(dispatch) {
131-
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions, ToastActions), dispatch);
166+
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions, ToastActions, SortingActions), dispatch);
132167
}
133168

134169
export default connect(mapStateToProps, mapDispatchToProps)(SketchList);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as ActionTypes from '../../../constants';
2+
import { DIRECTION } from '../actions/sorting';
3+
4+
const initialState = {
5+
field: 'createdAt',
6+
direction: DIRECTION.DESC
7+
};
8+
9+
const sorting = (state = initialState, action) => {
10+
switch (action.type) {
11+
case ActionTypes.TOGGLE_DIRECTION:
12+
if (action.field && action.field !== state.field) {
13+
if (action.field === 'name') {
14+
return { ...state, field: action.field, direction: DIRECTION.ASC };
15+
}
16+
return { ...state, field: action.field, direction: DIRECTION.DESC };
17+
}
18+
if (state.direction === DIRECTION.ASC) {
19+
return { ...state, direction: DIRECTION.DESC };
20+
}
21+
return { ...state, direction: DIRECTION.ASC };
22+
case ActionTypes.SET_SORTING:
23+
return { ...state, field: action.payload.field, direction: action.payload.direction };
24+
default:
25+
return state;
26+
}
27+
};
28+
29+
export default sorting;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { createSelector } from 'reselect';
2+
import differenceInMilliseconds from 'date-fns/difference_in_milliseconds';
3+
import orderBy from 'lodash/orderBy';
4+
import { DIRECTION } from '../actions/sorting';
5+
6+
const getSketches = state => state.sketches;
7+
const getField = state => state.sorting.field;
8+
const getDirection = state => state.sorting.direction;
9+
10+
const getSortedSketches = createSelector(
11+
getSketches,
12+
getField,
13+
getDirection,
14+
(sketches, field, direction) => {
15+
if (field === 'name') {
16+
if (direction === DIRECTION.DESC) {
17+
return orderBy(sketches, 'name', 'desc');
18+
}
19+
return orderBy(sketches, 'name', 'asc');
20+
}
21+
const sortedSketches = [...sketches].sort((a, b) => {
22+
const result =
23+
direction === DIRECTION.ASC
24+
? differenceInMilliseconds(new Date(a[field]), new Date(b[field]))
25+
: differenceInMilliseconds(new Date(b[field]), new Date(a[field]));
26+
return result;
27+
});
28+
return sortedSketches;
29+
}
30+
);
31+
32+
export default getSortedSketches;

client/reducers.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import sketches from './modules/IDE/reducers/projects';
1010
import toast from './modules/IDE/reducers/toast';
1111
import console from './modules/IDE/reducers/console';
1212
import assets from './modules/IDE/reducers/assets';
13+
import sorting from './modules/IDE/reducers/sorting';
1314
import loading from './modules/IDE/reducers/loading';
1415

1516
const rootReducer = combineReducers({
@@ -20,6 +21,7 @@ const rootReducer = combineReducers({
2021
user,
2122
project,
2223
sketches,
24+
sorting,
2325
editorAccessibility,
2426
toast,
2527
console,

client/styles/components/_sketch-list.scss

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,31 @@
1919
height: #{32 / $base-font-size}rem;
2020
}
2121

22+
.sketch-list__sort-button {
23+
display: flex;
24+
align-items: center;
25+
height: #{35 / $base-font-size}rem;
26+
& svg {
27+
@include themify() {
28+
fill: getThemifyVariable('inactive-text-color')
29+
}
30+
}
31+
}
32+
33+
.sketches-table__header {
34+
border-bottom: 2px dashed transparent;
35+
padding: #{3 / $base-font-size}rem 0;
36+
@include themify() {
37+
color: getThemifyVariable('inactive-text-color')
38+
}
39+
}
40+
41+
.sketches-table__header--selected {
42+
@include themify() {
43+
border-color: getThemifyVariable('logo-color');
44+
}
45+
}
46+
2247
.sketches-table__row {
2348
margin: #{10 / $base-font-size}rem;
2449
height: #{72 / $base-font-size}rem;

0 commit comments

Comments
 (0)