Skip to content
Snippets Groups Projects
Unverified Commit ca51b637 authored by Eric Rosenbaum's avatar Eric Rosenbaum Committed by GitHub
Browse files

Merge pull request #3209 from evhan55/multiple-alerts

Show multiple alerts in a stack and allow for custom alert icons.
parents d07f4726 18e35eff
No related branches found
No related tags found
No related merge requests found
...@@ -13,6 +13,12 @@ ...@@ -13,6 +13,12 @@
border-radius: 8px; border-radius: 8px;
padding: 12px; padding: 12px;
box-shadow: 2px 2px 2px 2px rgba(255, 140, 26, 0.25); box-shadow: 2px 2px 2px 2px rgba(255, 140, 26, 0.25);
margin-bottom: 7px;
}
.alert-icon {
vertical-align: middle;
margin-right: 5px;
} }
.alert-message { .alert-message {
......
import classNames from 'classnames';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Box from '../box/box.jsx'; import Box from '../box/box.jsx';
import Button from '../button/button.jsx'; import Button from '../button/button.jsx';
import styles from './alert.css'; import styles from './alert.css';
const Alerts = ({ const AlertComponent = ({
className, iconURL,
message, message,
onCloseAlert onCloseAlert
}) => ( }) => (
<Box <Box
bounds="parent" className={styles.alert}
className={classNames(className)}
> >
<Box <div className={styles.alertMessage}>
className={styles.alert} {iconURL ? (
<img
className={styles.alertIcon}
src={iconURL}
/>
) : null}
{message}
</div>
<Button
className={styles.alertRemoveButton}
onClick={onCloseAlert}
> >
<div className={styles.alertMessage}> {'x'}
{message} </Button>
</div>
<Button
className={styles.alertRemoveButton}
onClick={onCloseAlert}
>
{ /* eslint-disable react/jsx-no-literals */ }
x
</Button>
</Box>
</Box> </Box>
); );
Alerts.propTypes = { AlertComponent.propTypes = {
className: PropTypes.string, iconURL: PropTypes.string,
message: PropTypes.string.isRequired, message: PropTypes.string,
onCloseAlert: PropTypes.func.isRequired onCloseAlert: PropTypes.func.isRequired
}; };
export default Alerts; export default AlertComponent;
import React from 'react';
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import AlertComponent from '../components/alerts/alert.jsx';
class Alert extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleOnCloseAlert'
]);
}
handleOnCloseAlert () {
this.props.onCloseAlert(this.props.index);
}
render () {
return (
<AlertComponent
iconURL={this.props.iconURL}
message={this.props.message}
onCloseAlert={this.handleOnCloseAlert}
/>
);
}
}
Alert.propTypes = {
iconURL: PropTypes.string,
index: PropTypes.number,
message: PropTypes.string,
onCloseAlert: PropTypes.func.isRequired
};
export default Alert;
import classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import { import {
showAlert,
closeAlert closeAlert
} from '../reducers/alerts'; } from '../reducers/alerts';
import AlertsComponent from '../components/alerts/alerts.jsx'; import Box from '../components/box/box.jsx';
import Alert from '../containers/alert.jsx';
const Alerts = ({
alertsList,
className,
onCloseAlert
}) => (
<Box
bounds="parent"
className={classNames(className)}
>
{alertsList.map((a, index) => (
<Alert
iconURL={a.iconURL}
index={index}
key={index}
message={a.message}
onCloseAlert={onCloseAlert}
/>
))}
</Box>
);
Alerts.propTypes = {
alertsList: PropTypes.arrayOf(PropTypes.object),
className: PropTypes.string,
onCloseAlert: PropTypes.func
};
const mapStateToProps = state => ({ const mapStateToProps = state => ({
visible: state.scratchGui.alerts.visible, alertsList: state.scratchGui.alerts.alertsList
message: state.scratchGui.alerts.message
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onShowAlert: () => dispatch(showAlert()), onCloseAlert: index => dispatch(closeAlert(index))
onCloseAlert: () => dispatch(closeAlert())
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(AlertsComponent); )(Alerts);
...@@ -139,8 +139,8 @@ const vmListenerHOC = function (WrappedComponent) { ...@@ -139,8 +139,8 @@ const vmListenerHOC = function (WrappedComponent) {
onProjectRunStop: () => dispatch(setRunningState(false)), onProjectRunStop: () => dispatch(setRunningState(false)),
onTurboModeOn: () => dispatch(setTurboState(true)), onTurboModeOn: () => dispatch(setTurboState(true)),
onTurboModeOff: () => dispatch(setTurboState(false)), onTurboModeOff: () => dispatch(setTurboState(false)),
onShowAlert: () => { onShowAlert: data => {
dispatch(showAlert('Scratch has lost connection to peripheral.')); dispatch(showAlert(data));
} }
}); });
return connect( return connect(
......
import extensionData from '../lib/libraries/extensions/index.jsx';
const CLOSE_ALERT = 'scratch-gui/alerts/CLOSE_ALERT'; const CLOSE_ALERT = 'scratch-gui/alerts/CLOSE_ALERT';
const SHOW_ALERT = 'scratch-gui/alerts/SHOW_ALERT'; const SHOW_ALERT = 'scratch-gui/alerts/SHOW_ALERT';
const initialState = { const initialState = {
message: '', visible: true,
visible: false alertsList: []
}; };
const reducer = function (state, action) { const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState; if (typeof state === 'undefined') state = initialState;
switch (action.type) { switch (action.type) {
case SHOW_ALERT: case SHOW_ALERT: {
const newList = state.alertsList.slice();
const newAlert = {message: action.data.message};
const extensionId = action.data.extensionId;
if (extensionId) { // if it's an extension
const extension = extensionData.find(ext => ext.extensionId === extensionId);
if (extension && extension.name) {
// TODO: is this the right place to assemble this message?
newAlert.message = `${newAlert.message} ${extension.name}.`;
}
if (extension && extension.smallPeripheralImage) {
newAlert.iconURL = extension.smallPeripheralImage;
}
}
// TODO: add cases for other kinds of alerts here?
newList.push(newAlert);
return Object.assign({}, state, { return Object.assign({}, state, {
visible: true, alertsList: newList
message: action.message
}); });
case CLOSE_ALERT: }
case CLOSE_ALERT: {
const newList = state.alertsList.slice();
newList.splice(action.index, 1);
return Object.assign({}, state, { return Object.assign({}, state, {
visible: false alertsList: newList
}); });
}
default: default:
return state; return state;
} }
}; };
const closeAlert = function () { /**
return {type: CLOSE_ALERT}; * Function to close an alert with the given index.
*
* @param {object} index - the index of the alert to close.
* @return {object} - an object to be passed to the reducer.
*/
const closeAlert = function (index) {
return {
type: CLOSE_ALERT,
index
};
}; };
const showAlert = function (message) { /**
* Function to show an alert with the given input data.
*
* @param {object} data - data with the following props: {message, extensionId=null}
* @return {object} - an object to be passed to the reducer.
*/
const showAlert = function (data) {
return { return {
type: SHOW_ALERT, type: SHOW_ALERT,
message data
}; };
}; };
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment