Commit 98d2c0bd authored by Matija Obreza's avatar Matija Obreza
Browse files

Added global snackbar

parent c1f10f3b
Pipeline #5406 passed with stages
in 3 minutes and 52 seconds
import { SNACKBAR_OPEN, SNACKBAR_CLOSE } from 'constants/snackbar';
import { SHOW_SNACKBAR } from 'constants/snackbar';
export const showSnackbar = (content: string) => (dispatch) => {
dispatch({type: SNACKBAR_OPEN, payload: content});
setTimeout(() => {
dispatch({type: SNACKBAR_CLOSE});
}, 10000);
dispatch({type: SHOW_SNACKBAR, payload: content});
};
export const SNACKBAR_OPEN = 'SNACKBAR_OPEN';
export const SNACKBAR_CLOSE = 'SNACKBAR_CLOSE';
export const SHOW_SNACKBAR = 'SHOW_SNACKBAR';
import * as update from 'immutability-helper';
import { SNACKBAR_OPEN, SNACKBAR_CLOSE } from 'constants/snackbar';
import { SHOW_SNACKBAR } from 'constants/snackbar';
const INITIAL_STATE: {
open: boolean,
content: string,
content: any,
} = {
open: false,
content: 'No content',
content: null,
};
export default function snackbar(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
export default function snackbar(state = INITIAL_STATE, action: { type?: string, payload?: string } = {type: '', payload: null}) {
switch (action.type) {
case SNACKBAR_OPEN: {
case SHOW_SNACKBAR: {
if (! action.payload) {
return state;
}
return update(state, {
open: {$set: true},
content: {$set: action.payload},
});
}
case SNACKBAR_CLOSE: {
return update(state, {
open: {$set: false},
content: {$set: ''},
content: { $set: { message: action.payload, timestap: new Date().getTime() } },
});
}
......
......@@ -17,6 +17,7 @@ TimeAgo.locale(en);
import Header from './layout/Header';
import Footer from './layout/Footer';
import Snackbar from 'ui/common/snackbar/Snackbar';
import renderRoutes from 'ui/renderRoutes';
interface IAppProps extends React.ClassAttributes<any> {
......@@ -50,13 +51,14 @@ class App extends React.Component<IAppProps, any> {
}
public render() {
const { login, logoutRequest, loginAppRequest, route: { routes }} = this.props;
const { login, logoutRequest, loginAppRequest, route: { routes } } = this.props;
log('Rendering App view');
return (
<div>
<Header login={ login } logoutRequest={ logoutRequest } loginAppRequest={ loginAppRequest } />
{ renderRoutes(routes) }
<Footer />
<Snackbar />
</div>
);
}
......
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {withStyles} from 'material-ui/styles';
import {log} from 'utilities/debug';
import { showSnackbar } from 'actions/snackbar';
import ExpandFiltersComponent from './ExpandFiltersComponent';
import Button from 'material-ui/Button';
......@@ -14,7 +17,7 @@ const styles = (theme) => ({
//
// Renders a standard filters block
const FiltersBlock = ({ title, children, handleSubmit, onSubmit, initialize, classes }) => {
const FiltersBlock = ({ title, children, handleSubmit, onSubmit, initialize, classes, showSnackbar }) => {
const scrollToTop = () => {
if (window) {
window.scrollTo(0, 0);
......@@ -22,6 +25,7 @@ const FiltersBlock = ({ title, children, handleSubmit, onSubmit, initialize, cla
};
const onReset = (e) => {
showSnackbar('Resetting filters...');
log('Clearing form');
initialize({});
setTimeout(handleSubmit, 100);
......@@ -29,6 +33,7 @@ const FiltersBlock = ({ title, children, handleSubmit, onSubmit, initialize, cla
};
const processSubmit = handleSubmit((data) => {
showSnackbar('Applying filters...');
console.log('Submitting', data);
scrollToTop();
return onSubmit(cleanFilters(data));
......@@ -49,4 +54,8 @@ const FiltersBlock = ({ title, children, handleSubmit, onSubmit, initialize, cla
);
};
export default withStyles(styles)<any>(FiltersBlock);
const mapDispatchToProps = (dispatch) => bindActionCreators({
showSnackbar,
}, dispatch);
export default connect(null, mapDispatchToProps)(withStyles(styles)<any>(FiltersBlock));
import * as React from 'react';
import {connect} from 'react-redux';
import Snackbar_ from 'material-ui/Snackbar';
interface ISnacks {
snack: string;
}
/// Based on example from https://material-ui.com/demos/snackbars/#consecutive-snackbars
class Snackbar extends React.Component<ISnacks, any> {
private queue = [];
public constructor(props: any) {
super(props);
this.state = {
open: false,
messageInfo: {},
};
}
public componentWillMount() {
const { snack } = this.props;
console.log('Snack willMount', snack);
this.pushSnack(snack);
}
public componentWillReceiveProps(nextProps) {
const { snack } = nextProps;
console.log('Snack willReceive', snack);
this.pushSnack(snack);
}
private pushSnack(snack) {
if (! snack || ! snack.message) {
return;
}
this.queue.push({
message: snack.message,
key: new Date().getTime(),
});
if (this.state.open) {
// immediately begin dismissing current message to start showing new one
this.setState({ open: false });
} else {
this.processQueue();
}
}
private processQueue() {
if (this.queue.length > 0) {
this.setState({
messageInfo: this.queue.shift(),
open: true,
});
}
}
private handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ open: false });
}
private handleExited = () => {
this.processQueue();
}
public render() {
const { message, key } = this.state.messageInfo;
return (
<Snackbar_
key={ key }
anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
open={ this.state.open }
autoHideDuration={ 3000 }
onClose={ this.handleClose }
onExited={ this.handleExited }
SnackbarContentProps={ { 'aria-describedby': 'message-id' } }
message={ <span id="message-id">{ message }</span> }
/>
);
}
}
const mapStateToProps = (state) => ({
snack: state.snackbar.content,
});
export default connect(mapStateToProps, null)(Snackbar);
......@@ -9,7 +9,6 @@ import PrettyDate from 'ui/common/time/PrettyDate';
import Loading from 'ui/common/Loading';
import PasswordForm from './c/PasswordForm';
import Grid from 'material-ui/Grid';
import Snackbar from 'material-ui/Snackbar';
import Card, {CardContent, CardHeader} from 'ui/common/Card';
interface IProfilePageProps extends React.ClassAttributes<any> {
......@@ -17,8 +16,6 @@ interface IProfilePageProps extends React.ClassAttributes<any> {
loadUserProfile: () => void;
userProfile: User;
changePassword: (newPassword: string, oldPassword: string) => void;
snackbarOpen: boolean;
snackbarContent: string;
}
class ProfilePage extends React.Component<IProfilePageProps, any> {
......@@ -38,7 +35,7 @@ class ProfilePage extends React.Component<IProfilePageProps, any> {
}
public render() {
const { userProfile, snackbarOpen, snackbarContent } = this.props;
const { userProfile } = this.props;
const stillLoading: boolean = (! userProfile);
......@@ -83,13 +80,6 @@ class ProfilePage extends React.Component<IProfilePageProps, any> {
</Grid>
}
</Grid>
<Snackbar
anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
open={ snackbarOpen }
SnackbarContentProps={ { 'aria-describedby': 'message-id' } }
message={ <span id="message-id">{ snackbarContent }</span> }
/>
</div>
);
}
......@@ -97,8 +87,6 @@ class ProfilePage extends React.Component<IProfilePageProps, any> {
const mapStateToProps = (state) => ({
userProfile: state.userProfile.currentUserProfile,
snackbarOpen: state.snackbar.open,
snackbarContent: state.snackbar.content,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment