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