diff --git a/ui/src/App.tsx b/ui/src/App.tsx index bbacd44..fe4c59a 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { connect, ConnectedProps } from "react-redux"; import clsx from "clsx"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import { makeStyles } from "@material-ui/core/styles"; @@ -7,12 +8,19 @@ import Drawer from "@material-ui/core/Drawer"; import Toolbar from "@material-ui/core/Toolbar"; import List from "@material-ui/core/List"; import Typography from "@material-ui/core/Typography"; +import Snackbar from "@material-ui/core/Snackbar"; +import SnackbarContent from "@material-ui/core/SnackbarContent"; import IconButton from "@material-ui/core/IconButton"; +import Slide from "@material-ui/core/Slide"; +import { TransitionProps } from "@material-ui/core/transitions"; import MenuIcon from "@material-ui/icons/Menu"; import BarChartIcon from "@material-ui/icons/BarChart"; import LayersIcon from "@material-ui/icons/Layers"; import SettingsIcon from "@material-ui/icons/Settings"; +import CloseIcon from "@material-ui/icons/Close"; +import { AppState } from "./store"; import { paths } from "./paths"; +import { closeSnackbar } from "./actions/snackbarActions"; import ListItemLink from "./components/ListItemLink"; import SchedulersView from "./views/SchedulersView"; import DashboardView from "./views/DashboardView"; @@ -71,6 +79,13 @@ const useStyles = makeStyles((theme) => ({ width: theme.spacing(9), }, }, + snackbar: { + background: theme.palette.grey["A400"], + color: "#ffffff", + }, + snackbarCloseIcon: { + color: theme.palette.grey[400], + }, appBarSpacer: theme.mixins.toolbar, mainContainer: { display: "flex", @@ -96,7 +111,21 @@ const useStyles = makeStyles((theme) => ({ }, })); -function App() { +function mapStateToProps(state: AppState) { + return { snackbar: state.snackbar }; +} + +const mapDispatchToProps = { + closeSnackbar, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +function SlideUpTransition(props: TransitionProps) { + return ; +} + +function App(props: ConnectedProps) { const classes = useStyles(); const [open, setOpen] = useState(true); const toggleDrawer = () => { @@ -142,6 +171,31 @@ function App() { }} open={open} > + + + + + } + /> +
@@ -191,4 +245,4 @@ function App() { ); } -export default App; +export default connector(App); diff --git a/ui/src/actions/snackbarActions.ts b/ui/src/actions/snackbarActions.ts new file mode 100644 index 0000000..460533e --- /dev/null +++ b/ui/src/actions/snackbarActions.ts @@ -0,0 +1,12 @@ +export const CLOSE_SNACKBAR = "CLOSE_SNACKBAR"; + +interface CloseSnakbarAction { + type: typeof CLOSE_SNACKBAR; +} + +// Union of all snackbar related action types +export type SnackbarActionTypes = CloseSnakbarAction; + +export function closeSnackbar() { + return { type: CLOSE_SNACKBAR }; +} diff --git a/ui/src/reducers/snackbarReducer.ts b/ui/src/reducers/snackbarReducer.ts new file mode 100644 index 0000000..f8285b8 --- /dev/null +++ b/ui/src/reducers/snackbarReducer.ts @@ -0,0 +1,61 @@ +import { + CLOSE_SNACKBAR, + SnackbarActionTypes, +} from "../actions/snackbarActions"; +import { + DELETE_DEAD_TASK_SUCCESS, + DELETE_RETRY_TASK_SUCCESS, + DELETE_SCHEDULED_TASK_SUCCESS, + TasksActionTypes, +} from "../actions/tasksActions"; + +interface SnackbarState { + isOpen: boolean; + message: string; +} + +const initialState: SnackbarState = { + isOpen: false, + message: "", +}; + +function snackbarReducer( + state = initialState, + action: TasksActionTypes | SnackbarActionTypes +): SnackbarState { + switch (action.type) { + case CLOSE_SNACKBAR: + return { + // Note: We keep the message state unchanged for + // smoother transition animation. + ...state, + isOpen: false, + }; + + case DELETE_SCHEDULED_TASK_SUCCESS: + return { + isOpen: true, + // TODO: show only task id + message: `Scheduled task ${action.taskKey} deleted`, + }; + + case DELETE_RETRY_TASK_SUCCESS: + return { + isOpen: true, + // TODO: show only task id + message: `Retry task ${action.taskKey} deleted`, + }; + + case DELETE_DEAD_TASK_SUCCESS: + return { + isOpen: true, + // TODO: show only task id + message: `Dead task ${action.taskKey} deleted`, + }; + + default: + return state; + } +} + +export default snackbarReducer; diff --git a/ui/src/store.ts b/ui/src/store.ts index 3587b8e..975b32e 100644 --- a/ui/src/store.ts +++ b/ui/src/store.ts @@ -3,12 +3,14 @@ import settingsReducer from "./reducers/settingsReducer"; import queuesReducer from "./reducers/queuesReducer"; import tasksReducer from "./reducers/tasksReducer"; import schedulerEntriesReducer from "./reducers/schedulerEntriesReducer"; +import snackbarReducer from "./reducers/snackbarReducer"; const rootReducer = combineReducers({ settings: settingsReducer, queues: queuesReducer, tasks: tasksReducer, schedulerEntries: schedulerEntriesReducer, + snackbar: snackbarReducer, }); // AppState is the top-level application state maintained by redux store.