mirror of
https://github.com/xodio/xod.git
synced 2026-03-25 10:06:55 +01:00
refactor(errors, snackbar): do less dispatches, make logic easier but inside SnackBar container and make components dumbier
This commit is contained in:
@@ -22,5 +22,7 @@ export const EDITOR_SET_SELECTED_NODETYPE = 'EDITOR_SET_SELECTED_NODETYPE';
|
||||
|
||||
export const PROJECT_LOAD_DATA = 'PROJECT_LOAD_DATA';
|
||||
|
||||
export const ERROR = 'ERROR';
|
||||
export const ERROR_ADD = 'ERROR_ADD';
|
||||
export const ERROR_DELETE = 'ERROR_DELETE';
|
||||
|
||||
export const UPLOAD = 'UPLOAD';
|
||||
|
||||
@@ -3,41 +3,19 @@ import * as STATUS from './constants/statuses';
|
||||
import Selectors from './selectors';
|
||||
import { upload as uploadToEspruino } from 'xod-espruino/upload';
|
||||
|
||||
const ERROR_TIMEOUT = 3000;
|
||||
|
||||
const getTimestamp = () => new Date().getTime();
|
||||
|
||||
const setTimeoutForError = (id, dispatch) => {
|
||||
setTimeout(() => {
|
||||
dispatch({
|
||||
type: ActionType.ERROR,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
meta: {
|
||||
timestamp: getTimestamp(),
|
||||
status: STATUS.SUCCEEDED,
|
||||
},
|
||||
});
|
||||
}, ERROR_TIMEOUT);
|
||||
};
|
||||
|
||||
export const addError = (error) => (dispatch, getState) => {
|
||||
const errors = Selectors.Errors.getErrors(getState());
|
||||
const newErrorId = Selectors.Errors.getNewId(errors);
|
||||
dispatch({
|
||||
type: ActionType.ERROR,
|
||||
payload: error,
|
||||
meta: {
|
||||
timestamp: getTimestamp(),
|
||||
status: STATUS.STARTED,
|
||||
},
|
||||
});
|
||||
setTimeoutForError(newErrorId, dispatch);
|
||||
};
|
||||
export const addError = (error) => ({
|
||||
type: ActionType.ERROR_ADD,
|
||||
payload: error,
|
||||
meta: {
|
||||
timestamp: getTimestamp(),
|
||||
status: STATUS.STARTED,
|
||||
},
|
||||
});
|
||||
|
||||
export const deleteError = (id) => ({
|
||||
type: ActionType.ERROR,
|
||||
type: ActionType.ERROR_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
@@ -47,10 +25,6 @@ export const deleteError = (id) => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const keepError = (id) => (dispatch) => {
|
||||
setTimeoutForError(id, dispatch);
|
||||
};
|
||||
|
||||
export const moveNode = (id, position) => ({
|
||||
type: ActionType.NODE_MOVE,
|
||||
payload: {
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import R from 'ramda';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import * as STATUS from '../constants/statuses';
|
||||
import { checkForMouseBubbling } from '../utils/browser';
|
||||
|
||||
class SnackBarError extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
mouseOver: false,
|
||||
hidden: true,
|
||||
};
|
||||
|
||||
this.hide = this.hide.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onMouseOver = this.onMouseOver.bind(this);
|
||||
this.onMouseOut = this.onMouseOut.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -25,54 +20,10 @@ class SnackBarError extends React.Component {
|
||||
}, 5);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
nextProps.error.status === STATUS.SUCCEEDED &&
|
||||
!this.state.mouseOver
|
||||
) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (
|
||||
nextState.hidden === this.state.hidden ||
|
||||
(
|
||||
nextState.mouseOver &&
|
||||
nextProps.error.status === STATUS.SUCCEEDED
|
||||
)
|
||||
) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.hide();
|
||||
}
|
||||
onMouseOver() {
|
||||
if (this.state.mouseOver) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setMouseOver(true);
|
||||
}
|
||||
onMouseOut(event) {
|
||||
if (
|
||||
!this.state.mouseOver ||
|
||||
checkForMouseBubbling(event, this.refs.body)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setMouseOver(false);
|
||||
this.props.keepError(this.props.error.id);
|
||||
}
|
||||
|
||||
setMouseOver(val) {
|
||||
this.setState(
|
||||
R.assoc('mouseOver', val, this.state)
|
||||
);
|
||||
}
|
||||
setHidden(val) {
|
||||
this.setState(
|
||||
R.assoc('hidden', val, this.state)
|
||||
@@ -80,11 +31,10 @@ class SnackBarError extends React.Component {
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.setHidden(true);
|
||||
|
||||
setTimeout(() => {
|
||||
this.props.onHide(this.props.error.id);
|
||||
}, 500);
|
||||
return new Promise((resolve) => {
|
||||
this.setHidden(true);
|
||||
setTimeout(() => resolve(), 500);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -97,8 +47,6 @@ class SnackBarError extends React.Component {
|
||||
ref="body"
|
||||
className={cls}
|
||||
onClick={this.onClick}
|
||||
onMouseOver={this.onMouseOver}
|
||||
onMouseOut={this.onMouseOut}
|
||||
dataId={error.id}
|
||||
>
|
||||
<small>
|
||||
@@ -114,7 +62,6 @@ class SnackBarError extends React.Component {
|
||||
SnackBarError.propTypes = {
|
||||
error: React.PropTypes.object,
|
||||
onHide: React.PropTypes.func,
|
||||
keepError: React.PropTypes.func,
|
||||
};
|
||||
|
||||
export default SnackBarError;
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
const SnackBarList = ({ children }) => (
|
||||
<ul className="SnackBarList">
|
||||
const SnackBarList = ({ onMouseOver, onMouseOut, children }) => (
|
||||
<ul
|
||||
className="SnackBarList"
|
||||
onMouseOver={onMouseOver}
|
||||
onMouseOut={onMouseOut}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
|
||||
SnackBarList.propTypes = {
|
||||
children: React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
onMouseOver: React.PropTypes.func,
|
||||
onMouseOut: React.PropTypes.func,
|
||||
};
|
||||
|
||||
export default SnackBarList;
|
||||
|
||||
@@ -6,35 +6,114 @@ import { connect } from 'react-redux';
|
||||
import SnackBarList from '../components/SnackBarList';
|
||||
import SnackBarError from '../components/SnackBarError';
|
||||
import Selectors from '../selectors';
|
||||
import { deleteError, keepError } from '../actions';
|
||||
import { deleteError } from '../actions';
|
||||
|
||||
const ERROR_TIMEOUT = 3000;
|
||||
|
||||
class SnackBar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.errors = {};
|
||||
this.state = {
|
||||
mouseover: false,
|
||||
};
|
||||
|
||||
this.hideError = this.hideError.bind(this);
|
||||
this.keepError = this.keepError.bind(this);
|
||||
this.onMouseOver = this.onMouseOver.bind(this);
|
||||
this.onMouseOut = this.onMouseOut.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.addErrors(nextProps.errors);
|
||||
}
|
||||
|
||||
onMouseOver() {
|
||||
if (this.state.mouseover) return;
|
||||
|
||||
this.setState(
|
||||
R.assoc('mouseover', true, this.state)
|
||||
);
|
||||
}
|
||||
|
||||
onMouseOut() {
|
||||
if (!this.state.mouseover) return;
|
||||
|
||||
this.setState(
|
||||
R.assoc('mouseover', false, this.state)
|
||||
);
|
||||
|
||||
this.restartTimeouts();
|
||||
}
|
||||
|
||||
setTimeout(id) {
|
||||
return setTimeout(() => {
|
||||
if (!this.state.mouseover) {
|
||||
this.hideError(id);
|
||||
}
|
||||
}, ERROR_TIMEOUT);
|
||||
}
|
||||
|
||||
restartTimeouts() {
|
||||
R.pipe(
|
||||
R.values,
|
||||
R.forEach((error) => {
|
||||
const id = error.data.id;
|
||||
clearTimeout(error.timeout);
|
||||
this.errors[id].timeout = this.setTimeout(id);
|
||||
})
|
||||
)(this.errors);
|
||||
}
|
||||
|
||||
hideError(id) {
|
||||
this.props.deleteError(id);
|
||||
const element = this.errors[id].ref;
|
||||
|
||||
if (!element) return;
|
||||
|
||||
element
|
||||
.hide()
|
||||
.then(() => delete this.errors[id])
|
||||
.then(() => this.props.deleteError(id));
|
||||
}
|
||||
keepError(id) {
|
||||
this.props.keepError(id);
|
||||
|
||||
addErrors(errors) {
|
||||
R.pipe(
|
||||
R.values,
|
||||
R.forEach((error) => {
|
||||
if (this.errors.hasOwnProperty(error.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const assignRef = (el) => {
|
||||
if (this.errors.hasOwnProperty(error.id)) {
|
||||
this.errors[error.id].ref = el;
|
||||
}
|
||||
};
|
||||
|
||||
this.errors[error.id] = {
|
||||
timeout: this.setTimeout(error.id),
|
||||
data: error,
|
||||
element: (
|
||||
<SnackBarError
|
||||
ref={assignRef}
|
||||
key={error.id}
|
||||
onHide={this.hideError}
|
||||
error={error}
|
||||
/>
|
||||
),
|
||||
};
|
||||
})
|
||||
)(errors);
|
||||
}
|
||||
|
||||
render() {
|
||||
const errors = R.values(this.props.errors);
|
||||
const errors = R.values(this.errors);
|
||||
return (
|
||||
<SnackBarList>
|
||||
{errors.map((error) =>
|
||||
<SnackBarError
|
||||
key={error.id}
|
||||
onHide={this.hideError}
|
||||
keepError={this.keepError}
|
||||
error={error}
|
||||
/>
|
||||
)}
|
||||
<SnackBarList
|
||||
onMouseOver={this.onMouseOver}
|
||||
onMouseOut={this.onMouseOut}
|
||||
>
|
||||
{errors.map(error => error.element)}
|
||||
</SnackBarList>
|
||||
);
|
||||
}
|
||||
@@ -44,7 +123,6 @@ class SnackBar extends React.Component {
|
||||
SnackBar.propTypes = {
|
||||
errors: React.PropTypes.object,
|
||||
deleteError: React.PropTypes.func,
|
||||
keepError: React.PropTypes.func,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
@@ -53,7 +131,6 @@ const mapStateToProps = (state) => ({
|
||||
|
||||
const mapDispatchToProps = (dispatch) => (bindActionCreators({
|
||||
deleteError,
|
||||
keepError,
|
||||
}, dispatch));
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SnackBar);
|
||||
|
||||
@@ -1,48 +1,24 @@
|
||||
import R from 'ramda';
|
||||
import { ERROR } from '../actionTypes';
|
||||
import * as STATUS from '../constants/statuses';
|
||||
import { ERROR_ADD, ERROR_DELETE } from '../actionTypes';
|
||||
import { getNewId } from '../selectors/errors';
|
||||
|
||||
export const errorsReducer = (errors = {}, action) => {
|
||||
if (
|
||||
action.type === ERROR &&
|
||||
action.meta &&
|
||||
action.meta.status
|
||||
) {
|
||||
switch (action.meta.status) {
|
||||
case STATUS.STARTED: {
|
||||
const newId = getNewId(errors);
|
||||
return R.assoc(
|
||||
newId,
|
||||
{
|
||||
id: newId,
|
||||
timestamp: action.meta.timestamp,
|
||||
status: action.meta.status,
|
||||
payload: action.payload,
|
||||
},
|
||||
errors
|
||||
);
|
||||
}
|
||||
case STATUS.SUCCEEDED: {
|
||||
if (!errors.hasOwnProperty(action.payload.id)) { return errors; }
|
||||
|
||||
return R.assoc(
|
||||
action.payload.id,
|
||||
R.merge(
|
||||
errors[action.payload.id],
|
||||
{
|
||||
timestamp: action.meta.timestamp,
|
||||
status: action.meta.status,
|
||||
}
|
||||
),
|
||||
errors
|
||||
);
|
||||
}
|
||||
case STATUS.DELETED:
|
||||
return R.omit([action.payload.id.toString()], errors);
|
||||
default: break;
|
||||
switch (action.type) {
|
||||
case ERROR_ADD: {
|
||||
const newId = getNewId(errors);
|
||||
return R.assoc(
|
||||
newId,
|
||||
{
|
||||
id: newId,
|
||||
timestamp: action.meta.timestamp,
|
||||
payload: action.payload,
|
||||
},
|
||||
errors
|
||||
);
|
||||
}
|
||||
case ERROR_DELETE:
|
||||
return R.omit([action.payload.id.toString()], errors);
|
||||
default:
|
||||
return errors;
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user