Add queued snackbar, fix progress reducer
[arvados-workbench2.git] / src / views-components / snackbar / snackbar.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from "react";
6 import { connect } from "react-redux";
7 import { RootState } from "~/store/store";
8 import MaterialSnackbar, { SnackbarProps } from "@material-ui/core/Snackbar";
9 import { Dispatch } from "redux";
10 import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
11 import IconButton from '@material-ui/core/IconButton';
12 import SnackbarContent from '@material-ui/core/SnackbarContent';
13 import WarningIcon from '@material-ui/icons/Warning';
14 import CheckCircleIcon from '@material-ui/icons/CheckCircle';
15 import ErrorIcon from '@material-ui/icons/Error';
16 import InfoIcon from '@material-ui/icons/Info';
17 import CloseIcon from '@material-ui/icons/Close';
18 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
19 import { ArvadosTheme } from "~/common/custom-theme";
20 import { amber, green } from "@material-ui/core/colors";
21 import * as classNames from 'classnames';
22
23 const mapStateToProps = (state: RootState): SnackbarProps & ArvadosSnackbarProps => {
24     const messages = state.snackbar.messages;
25     return {
26         anchorOrigin: { vertical: "bottom", horizontal: "right" },
27         open: state.snackbar.open,
28         message: <span>{messages.length > 0 ? messages[0].message : ""}</span>,
29         autoHideDuration: messages.length > 0 ? messages[0].hideDuration : 0,
30         kind: messages.length > 0 ? messages[0].kind : SnackbarKind.INFO
31     };
32 };
33
34 const mapDispatchToProps = (dispatch: Dispatch) => ({
35     onClose: (event: any, reason: string) => {
36         if (reason !== "clickaway") {
37             dispatch(snackbarActions.CLOSE_SNACKBAR());
38         }
39     },
40     onExited: () => {
41         dispatch(snackbarActions.SHIFT_MESSAGES());
42     }
43 });
44
45 const ArvadosSnackbar = (props: any) => <MaterialSnackbar {...props}>
46     <ArvadosSnackbarContent {...props}/>
47 </MaterialSnackbar>;
48
49 type CssRules = "success" | "error" | "info" | "warning" | "icon" | "iconVariant" | "message";
50
51 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
52     success: {
53         backgroundColor: green[600]
54     },
55     error: {
56         backgroundColor: theme.palette.error.dark
57     },
58     info: {
59         backgroundColor: theme.palette.primary.dark
60     },
61     warning: {
62         backgroundColor: amber[700]
63     },
64     icon: {
65         fontSize: 20
66     },
67     iconVariant: {
68         opacity: 0.9,
69         marginRight: theme.spacing.unit
70     },
71     message: {
72         display: 'flex',
73         alignItems: 'center'
74     },
75 });
76
77 interface ArvadosSnackbarProps {
78     kind: SnackbarKind;
79 }
80
81 const ArvadosSnackbarContent = (props: SnackbarProps & ArvadosSnackbarProps & WithStyles<CssRules>) => {
82     const { classes, className, message, onClose, kind } = props;
83
84     let Icon = InfoIcon;
85     let cssClass = classes.info;
86
87     switch (kind) {
88         case SnackbarKind.INFO:
89             Icon = InfoIcon;
90             cssClass = classes.info;
91             break;
92         case SnackbarKind.WARNING:
93             Icon = WarningIcon;
94             cssClass = classes.warning;
95             break;
96         case SnackbarKind.SUCCESS:
97             Icon = CheckCircleIcon;
98             cssClass = classes.success;
99             break;
100         case SnackbarKind.ERROR:
101             Icon = ErrorIcon;
102             cssClass = classes.error;
103             break;
104     }
105
106     return (
107         <SnackbarContent
108             className={classNames(cssClass, className)}
109             aria-describedby="client-snackbar"
110             message={
111                 <span id="client-snackbar" className={classes.message}>
112                     <Icon className={classNames(classes.icon, classes.iconVariant)}/>
113                     {message}
114                 </span>
115             }
116             action={
117                 <IconButton
118                     key="close"
119                     aria-label="Close"
120                     color="inherit"
121                     onClick={e => {
122                         if (onClose) {
123                             onClose(e, '');
124                         }
125                     }}>
126                     <CloseIcon className={classes.icon}/>
127                 </IconButton>
128             }
129         />
130     );
131 };
132
133 export const Snackbar = connect(mapStateToProps, mapDispatchToProps)(
134     withStyles(styles)(ArvadosSnackbar)
135 );