From 98d2c0bd50aa18a3d13e23421705a65d6a7259ac Mon Sep 17 00:00:00 2001 From: Matija Obreza Date: Mon, 18 Jun 2018 16:20:53 +0200 Subject: [PATCH] Added global snackbar --- src/actions/snackbar.ts | 7 +- src/constants/snackbar.ts | 3 +- src/reducers/snackbar.ts | 24 +++---- src/ui/App.tsx | 4 +- src/ui/common/filter/FiltersBlock.tsx | 13 +++- src/ui/common/snackbar/Snackbar.tsx | 95 +++++++++++++++++++++++++++ src/ui/pages/profile/ProfilePage.tsx | 14 +--- 7 files changed, 122 insertions(+), 38 deletions(-) create mode 100644 src/ui/common/snackbar/Snackbar.tsx diff --git a/src/actions/snackbar.ts b/src/actions/snackbar.ts index 8da6348..f503054 100644 --- a/src/actions/snackbar.ts +++ b/src/actions/snackbar.ts @@ -1,8 +1,5 @@ -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}); }; diff --git a/src/constants/snackbar.ts b/src/constants/snackbar.ts index 79a6e8e..fbd6291 100644 --- a/src/constants/snackbar.ts +++ b/src/constants/snackbar.ts @@ -1,2 +1 @@ -export const SNACKBAR_OPEN = 'SNACKBAR_OPEN'; -export const SNACKBAR_CLOSE = 'SNACKBAR_CLOSE'; +export const SHOW_SNACKBAR = 'SHOW_SNACKBAR'; diff --git a/src/reducers/snackbar.ts b/src/reducers/snackbar.ts index d5a18d0..201ccbe 100644 --- a/src/reducers/snackbar.ts +++ b/src/reducers/snackbar.ts @@ -1,27 +1,21 @@ 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() } }, }); } diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 4e9d3e6..b6745a2 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -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 { @@ -50,13 +51,14 @@ class App extends React.Component { } public render() { - const { login, logoutRequest, loginAppRequest, route: { routes }} = this.props; + const { login, logoutRequest, loginAppRequest, route: { routes } } = this.props; log('Rendering App view'); return (
{ renderRoutes(routes) }
+
); } diff --git a/src/ui/common/filter/FiltersBlock.tsx b/src/ui/common/filter/FiltersBlock.tsx index e001b2a..4970f5f 100644 --- a/src/ui/common/filter/FiltersBlock.tsx +++ b/src/ui/common/filter/FiltersBlock.tsx @@ -1,7 +1,10 @@ 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)(FiltersBlock); +const mapDispatchToProps = (dispatch) => bindActionCreators({ + showSnackbar, +}, dispatch); + +export default connect(null, mapDispatchToProps)(withStyles(styles)(FiltersBlock)); diff --git a/src/ui/common/snackbar/Snackbar.tsx b/src/ui/common/snackbar/Snackbar.tsx new file mode 100644 index 0000000..c5d1e3c --- /dev/null +++ b/src/ui/common/snackbar/Snackbar.tsx @@ -0,0 +1,95 @@ +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 { + + 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 ( + { message } } + /> + ); + } +} + +const mapStateToProps = (state) => ({ + snack: state.snackbar.content, +}); + +export default connect(mapStateToProps, null)(Snackbar); diff --git a/src/ui/pages/profile/ProfilePage.tsx b/src/ui/pages/profile/ProfilePage.tsx index c9fe58f..ce43336 100644 --- a/src/ui/pages/profile/ProfilePage.tsx +++ b/src/ui/pages/profile/ProfilePage.tsx @@ -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 { @@ -17,8 +16,6 @@ interface IProfilePageProps extends React.ClassAttributes { loadUserProfile: () => void; userProfile: User; changePassword: (newPassword: string, oldPassword: string) => void; - snackbarOpen: boolean; - snackbarContent: string; } class ProfilePage extends React.Component { @@ -38,7 +35,7 @@ class ProfilePage extends React.Component { } 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 { } - - { snackbarContent } } - /> ); } @@ -97,8 +87,6 @@ class ProfilePage extends React.Component { const mapStateToProps = (state) => ({ userProfile: state.userProfile.currentUserProfile, - snackbarOpen: state.snackbar.open, - snackbarContent: state.snackbar.content, }); const mapDispatchToProps = (dispatch) => bindActionCreators({ -- GitLab