Merge branch 'master' into 14504-users-admin-panel
[arvados.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 { Dispatch } from "redux";
7 import { connect } from "react-redux";
8 import { RootState } from "~/store/store";
9 import { Button, IconButton, StyleRulesCallback, WithStyles, withStyles, SnackbarContent } from '@material-ui/core';
10 import MaterialSnackbar, { SnackbarOrigin } from "@material-ui/core/Snackbar";
11 import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
12 import { navigateToProject } from '~/store/navigation/navigation-action';
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 { ArvadosTheme } from "~/common/custom-theme";
19 import { amber, green } from "@material-ui/core/colors";
20 import * as classNames from 'classnames';
21
22 interface SnackbarDataProps {
23     anchorOrigin?: SnackbarOrigin;
24     autoHideDuration?: number;
25     open: boolean;
26     message?: React.ReactElement<any>;
27     kind: SnackbarKind;
28     link?: string;
29 }
30
31 interface SnackbarEventProps {
32     onClose?: (event: React.SyntheticEvent<any>, reason: string) => void;
33     onExited: () => void;
34     onClick: (uuid: string) => void;
35 }
36
37 const mapStateToProps = (state: RootState): SnackbarDataProps => {
38     const messages = state.snackbar.messages;
39     return {
40         anchorOrigin: { vertical: "bottom", horizontal: "right" },
41         open: state.snackbar.open,
42         message: <span>{messages.length > 0 ? messages[0].message : ""}</span>,
43         autoHideDuration: messages.length > 0 ? messages[0].hideDuration : 0,
44         kind: messages.length > 0 ? messages[0].kind : SnackbarKind.INFO,
45         link: messages.length > 0 ? messages[0].link : ''
46     };
47 };
48
49 const mapDispatchToProps = (dispatch: Dispatch): SnackbarEventProps => ({
50     onClose: (event: any, reason: string) => {
51         if (reason !== "clickaway") {
52             dispatch(snackbarActions.CLOSE_SNACKBAR());
53         }
54     },
55     onExited: () => {
56         dispatch(snackbarActions.SHIFT_MESSAGES());
57     },
58     onClick: (uuid: string) => {
59         dispatch(navigateToProject(uuid));
60     }
61 });
62
63 type CssRules = "success" | "error" | "info" | "warning" | "icon" | "iconVariant" | "message" | "linkButton";
64
65 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
66     success: {
67         backgroundColor: green[600]
68     },
69     error: {
70         backgroundColor: theme.palette.error.dark
71     },
72     info: {
73         backgroundColor: theme.palette.primary.main
74     },
75     warning: {
76         backgroundColor: amber[700]
77     },
78     icon: {
79         fontSize: 20
80     },
81     iconVariant: {
82         opacity: 0.9,
83         marginRight: theme.spacing.unit
84     },
85     message: {
86         display: 'flex',
87         alignItems: 'center'
88     },
89     linkButton: {
90         fontWeight: 'bolder'
91     }
92 });
93
94 type SnackbarProps = SnackbarDataProps & SnackbarEventProps & WithStyles<CssRules>;
95
96 export const Snackbar = withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(
97     (props: SnackbarProps) => {
98         const { classes } = props;
99
100         const variants = {
101             [SnackbarKind.INFO]: [InfoIcon, classes.info],
102             [SnackbarKind.WARNING]: [WarningIcon, classes.warning],
103             [SnackbarKind.SUCCESS]: [CheckCircleIcon, classes.success],
104             [SnackbarKind.ERROR]: [ErrorIcon, classes.error]
105         };
106
107         const [Icon, cssClass] = variants[props.kind];
108
109
110
111         return (
112             <MaterialSnackbar
113                 open={props.open}
114                 message={props.message}
115                 onClose={props.onClose}
116                 onExited={props.onExited}
117                 anchorOrigin={props.anchorOrigin}
118                 autoHideDuration={props.autoHideDuration}>
119                 <SnackbarContent
120                     className={classNames(cssClass)}
121                     aria-describedby="client-snackbar"
122                     message={
123                         <span id="client-snackbar" className={classes.message}>
124                             <Icon className={classNames(classes.icon, classes.iconVariant)}/>
125                             {props.message}
126                         </span>
127                     }
128                     action={actions(props)}
129                 />
130             </MaterialSnackbar>
131         );
132     }
133 ));
134
135 const actions = (props: SnackbarProps) => {
136     const { link, onClose, onClick, classes } = props;
137     const actions = [
138         <IconButton
139             key="close"
140             aria-label="Close"
141             color="inherit"
142             onClick={e => onClose && onClose(e, '')}>
143             <CloseIcon className={classes.icon} />
144         </IconButton>
145     ];
146     if (link) {
147         actions.splice(0, 0,
148             <Button key="goTo"
149                 aria-label="goTo"
150                 size="small"
151                 color="inherit"
152                 className={classes.linkButton}
153                 onClick={() => onClick(link) }>
154                 Go To
155             </Button>
156         );
157     }
158     return actions;
159 };