* 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
This commit is contained in:
parent
6f1b6fd51c
commit
8caeb0d439
12 changed files with 219 additions and 22 deletions
|
@ -121,6 +121,9 @@ export const HIDE_RUNTIME_ERROR_WARNING = 'HIDE_RUNTIME_ERROR_WARNING';
|
|||
export const SHOW_RUNTIME_ERROR_WARNING = 'SHOW_RUNTIME_ERROR_WARNING';
|
||||
export const SET_ASSETS = 'SET_ASSETS';
|
||||
|
||||
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
|
||||
export const SET_SORTING = 'SET_SORTING';
|
||||
|
||||
export const START_LOADING = 'START_LOADING';
|
||||
export const STOP_LOADING = 'STOP_LOADING';
|
||||
|
||||
|
|
9
client/images/sort-arrow-down.svg
Normal file
9
client/images/sort-arrow-down.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="40px" height="30px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M49.761,67.969l-17.36-30.241h34.561L49.761,67.969z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 568 B |
9
client/images/sort-arrow-up.svg
Normal file
9
client/images/sort-arrow-up.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="40px" height="30px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M49.761,37.728l17.36,30.241H32.561L49.761,37.728z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 567 B |
|
@ -13,6 +13,7 @@ require('./images/p5js-square-logo.png');
|
|||
|
||||
const history = browserHistory;
|
||||
const initialState = window.__INITIAL_STATE__;
|
||||
|
||||
const store = configureStore(initialState);
|
||||
|
||||
const App = () => (
|
||||
|
|
27
client/modules/IDE/actions/sorting.js
Normal file
27
client/modules/IDE/actions/sorting.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
export const DIRECTION = {
|
||||
ASC: 'ASCENDING',
|
||||
DESC: 'DESCENDING'
|
||||
};
|
||||
|
||||
export function setSorting(field, direction) {
|
||||
return {
|
||||
type: ActionTypes.SET_SORTING,
|
||||
payload: {
|
||||
field,
|
||||
direction
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function resetSorting() {
|
||||
return setSorting('createdAt', DIRECTION.DESC);
|
||||
}
|
||||
|
||||
export function toggleDirectionForField(field) {
|
||||
return {
|
||||
type: ActionTypes.TOGGLE_DIRECTION,
|
||||
field
|
||||
};
|
||||
}
|
|
@ -6,17 +6,24 @@ import InlineSVG from 'react-inlinesvg';
|
|||
import { connect } from 'react-redux';
|
||||
import { browserHistory, Link } from 'react-router';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import classNames from 'classnames';
|
||||
import * as ProjectActions from '../actions/project';
|
||||
import * as SketchActions from '../actions/projects';
|
||||
import * as ToastActions from '../actions/toast';
|
||||
import * as SortingActions from '../actions/sorting';
|
||||
import getSortedSketches from '../selectors/projects';
|
||||
import Loader from '../../App/components/loader';
|
||||
|
||||
const trashCan = require('../../../images/trash-can.svg');
|
||||
const arrowUp = require('../../../images/sort-arrow-up.svg');
|
||||
const arrowDown = require('../../../images/sort-arrow-down.svg');
|
||||
|
||||
class SketchList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.props.getProjects(this.props.username);
|
||||
this.props.resetSorting();
|
||||
this._renderFieldHeader = this._renderFieldHeader.bind(this);
|
||||
}
|
||||
|
||||
getSketchesTitle() {
|
||||
|
@ -30,18 +37,39 @@ class SketchList extends React.Component {
|
|||
return !this.props.loading && this.props.sketches.length > 0;
|
||||
}
|
||||
|
||||
renderLoader() {
|
||||
_renderLoader() {
|
||||
if (this.props.loading) return <Loader />;
|
||||
return null;
|
||||
}
|
||||
|
||||
renderEmptyTable() {
|
||||
_renderEmptyTable() {
|
||||
if (!this.props.loading && this.props.sketches.length === 0) {
|
||||
return (<p className="sketches-table__empty">No sketches.</p>);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_renderFieldHeader(fieldName, displayName) {
|
||||
const { field, direction } = this.props.sorting;
|
||||
const headerClass = classNames({
|
||||
'sketches-table__header': true,
|
||||
'sketches-table__header--selected': field === fieldName
|
||||
});
|
||||
return (
|
||||
<th scope="col">
|
||||
<button className="sketch-list__sort-button" onClick={() => this.props.toggleDirectionForField(fieldName)}>
|
||||
<span className={headerClass}>{displayName}</span>
|
||||
{field === fieldName && direction === SortingActions.DIRECTION.ASC &&
|
||||
<InlineSVG src={arrowUp} />
|
||||
}
|
||||
{field === fieldName && direction === SortingActions.DIRECTION.DESC &&
|
||||
<InlineSVG src={arrowDown} />
|
||||
}
|
||||
</button>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
|
||||
return (
|
||||
|
@ -49,16 +77,16 @@ class SketchList extends React.Component {
|
|||
<Helmet>
|
||||
<title>{this.getSketchesTitle()}</title>
|
||||
</Helmet>
|
||||
{this.renderLoader()}
|
||||
{this.renderEmptyTable()}
|
||||
{this._renderLoader()}
|
||||
{this._renderEmptyTable()}
|
||||
{this.hasSketches() &&
|
||||
<table className="sketches-table" summary="table containing all saved projects">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="sketch-list__trash-column" scope="col"></th>
|
||||
<th scope="col">Sketch</th>
|
||||
<th scope="col">Date created</th>
|
||||
<th scope="col">Date updated</th>
|
||||
{this._renderFieldHeader('name', 'Sketch')}
|
||||
{this._renderFieldHeader('createdAt', 'Date Created')}
|
||||
{this._renderFieldHeader('updatedAt', 'Date Updated')}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -112,7 +140,13 @@ SketchList.propTypes = {
|
|||
})).isRequired,
|
||||
username: PropTypes.string,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
deleteProject: PropTypes.func.isRequired
|
||||
deleteProject: PropTypes.func.isRequired,
|
||||
toggleDirectionForField: PropTypes.func.isRequired,
|
||||
resetSorting: PropTypes.func.isRequired,
|
||||
sorting: PropTypes.shape({
|
||||
field: PropTypes.string.isRequired,
|
||||
direction: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
SketchList.defaultProps = {
|
||||
|
@ -122,13 +156,14 @@ SketchList.defaultProps = {
|
|||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.user,
|
||||
sketches: state.sketches,
|
||||
loading: state.loading,
|
||||
sketches: getSortedSketches(state),
|
||||
sorting: state.sorting,
|
||||
loading: state.loading
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions, ToastActions), dispatch);
|
||||
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions, ToastActions, SortingActions), dispatch);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SketchList);
|
||||
|
|
29
client/modules/IDE/reducers/sorting.js
Normal file
29
client/modules/IDE/reducers/sorting.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
import { DIRECTION } from '../actions/sorting';
|
||||
|
||||
const initialState = {
|
||||
field: 'createdAt',
|
||||
direction: DIRECTION.DESC
|
||||
};
|
||||
|
||||
const sorting = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.TOGGLE_DIRECTION:
|
||||
if (action.field && action.field !== state.field) {
|
||||
if (action.field === 'name') {
|
||||
return { ...state, field: action.field, direction: DIRECTION.ASC };
|
||||
}
|
||||
return { ...state, field: action.field, direction: DIRECTION.DESC };
|
||||
}
|
||||
if (state.direction === DIRECTION.ASC) {
|
||||
return { ...state, direction: DIRECTION.DESC };
|
||||
}
|
||||
return { ...state, direction: DIRECTION.ASC };
|
||||
case ActionTypes.SET_SORTING:
|
||||
return { ...state, field: action.payload.field, direction: action.payload.direction };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default sorting;
|
32
client/modules/IDE/selectors/projects.js
Normal file
32
client/modules/IDE/selectors/projects.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import differenceInMilliseconds from 'date-fns/difference_in_milliseconds';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { DIRECTION } from '../actions/sorting';
|
||||
|
||||
const getSketches = state => state.sketches;
|
||||
const getField = state => state.sorting.field;
|
||||
const getDirection = state => state.sorting.direction;
|
||||
|
||||
const getSortedSketches = createSelector(
|
||||
getSketches,
|
||||
getField,
|
||||
getDirection,
|
||||
(sketches, field, direction) => {
|
||||
if (field === 'name') {
|
||||
if (direction === DIRECTION.DESC) {
|
||||
return orderBy(sketches, 'name', 'desc');
|
||||
}
|
||||
return orderBy(sketches, 'name', 'asc');
|
||||
}
|
||||
const sortedSketches = [...sketches].sort((a, b) => {
|
||||
const result =
|
||||
direction === DIRECTION.ASC
|
||||
? differenceInMilliseconds(new Date(a[field]), new Date(b[field]))
|
||||
: differenceInMilliseconds(new Date(b[field]), new Date(a[field]));
|
||||
return result;
|
||||
});
|
||||
return sortedSketches;
|
||||
}
|
||||
);
|
||||
|
||||
export default getSortedSketches;
|
|
@ -10,6 +10,7 @@ import sketches from './modules/IDE/reducers/projects';
|
|||
import toast from './modules/IDE/reducers/toast';
|
||||
import console from './modules/IDE/reducers/console';
|
||||
import assets from './modules/IDE/reducers/assets';
|
||||
import sorting from './modules/IDE/reducers/sorting';
|
||||
import loading from './modules/IDE/reducers/loading';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
|
@ -20,6 +21,7 @@ const rootReducer = combineReducers({
|
|||
user,
|
||||
project,
|
||||
sketches,
|
||||
sorting,
|
||||
editorAccessibility,
|
||||
toast,
|
||||
console,
|
||||
|
|
|
@ -19,6 +19,31 @@
|
|||
height: #{32 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.sketch-list__sort-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: #{35 / $base-font-size}rem;
|
||||
& svg {
|
||||
@include themify() {
|
||||
fill: getThemifyVariable('inactive-text-color')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sketches-table__header {
|
||||
border-bottom: 2px dashed transparent;
|
||||
padding: #{3 / $base-font-size}rem 0;
|
||||
@include themify() {
|
||||
color: getThemifyVariable('inactive-text-color')
|
||||
}
|
||||
}
|
||||
|
||||
.sketches-table__header--selected {
|
||||
@include themify() {
|
||||
border-color: getThemifyVariable('logo-color');
|
||||
}
|
||||
}
|
||||
|
||||
.sketches-table__row {
|
||||
margin: #{10 / $base-font-size}rem;
|
||||
height: #{72 / $base-font-size}rem;
|
||||
|
|
46
package-lock.json
generated
46
package-lock.json
generated
|
@ -5462,7 +5462,8 @@
|
|||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
@ -5480,11 +5481,13 @@
|
|||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -5497,15 +5500,18 @@
|
|||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -5608,7 +5614,8 @@
|
|||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -5618,6 +5625,7 @@
|
|||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
@ -5630,17 +5638,20 @@
|
|||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
|
@ -5657,6 +5668,7 @@
|
|||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -5729,7 +5741,8 @@
|
|||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -5739,6 +5752,7 @@
|
|||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -5814,7 +5828,8 @@
|
|||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
@ -5844,6 +5859,7 @@
|
|||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
@ -5861,6 +5877,7 @@
|
|||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
@ -5899,11 +5916,13 @@
|
|||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -14100,6 +14119,11 @@
|
|||
"semver": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"reselect": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
|
||||
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz",
|
||||
|
|
|
@ -161,6 +161,7 @@
|
|||
"redux-thunk": "^2.3.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise": "^4.1.1",
|
||||
"reselect": "^4.0.0",
|
||||
"s3": "^4.4.0",
|
||||
"s3-policy": "^0.2.0",
|
||||
"sass-extract": "^2.1.0",
|
||||
|
|
Loading…
Reference in a new issue