Add dark mode

This commit is contained in:
Peizhi Zheng 2021-01-12 15:55:56 -08:00 committed by Ken Hibino
parent 60391cdfc3
commit b15fa59cf9
6 changed files with 218 additions and 176 deletions

View File

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { connect, ConnectedProps } from "react-redux"; import { connect, ConnectedProps } from "react-redux";
import clsx from "clsx"; import clsx from "clsx";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles, ThemeProvider } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar"; import AppBar from "@material-ui/core/AppBar";
import Drawer from "@material-ui/core/Drawer"; import Drawer from "@material-ui/core/Drawer";
import Toolbar from "@material-ui/core/Toolbar"; import Toolbar from "@material-ui/core/Toolbar";
@ -26,6 +26,7 @@ import DoubleArrowIcon from "@material-ui/icons/DoubleArrow";
import CloseIcon from "@material-ui/icons/Close"; import CloseIcon from "@material-ui/icons/Close";
import { AppState } from "./store"; import { AppState } from "./store";
import { paths } from "./paths"; import { paths } from "./paths";
import { makeTheme } from "./theme";
import { closeSnackbar } from "./actions/snackbarActions"; import { closeSnackbar } from "./actions/snackbarActions";
import ListItemLink from "./components/ListItemLink"; import ListItemLink from "./components/ListItemLink";
import SchedulersView from "./views/SchedulersView"; import SchedulersView from "./views/SchedulersView";
@ -125,7 +126,10 @@ const useStyles = makeStyles((theme) => ({
})); }));
function mapStateToProps(state: AppState) { function mapStateToProps(state: AppState) {
return { snackbar: state.snackbar }; return {
snackbar: state.snackbar,
isDarkTheme: state.settings.isDarkTheme,
};
} }
const mapDispatchToProps = { const mapDispatchToProps = {
@ -141,151 +145,152 @@ function SlideUpTransition(props: TransitionProps) {
function App(props: ConnectedProps<typeof connector>) { function App(props: ConnectedProps<typeof connector>) {
const classes = useStyles(); const classes = useStyles();
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const toggleDrawer = () => { const toggleDrawer = () => setOpen(!open);
setOpen(!open); const theme = makeTheme(props.isDarkTheme);
};
return ( return (
<Router> <ThemeProvider theme={theme}>
<div className={classes.root}> <Router>
<AppBar <div className={classes.root}>
position="absolute" <AppBar
className={classes.appBar} position="absolute"
variant="outlined" className={classes.appBar}
> variant="outlined"
<Toolbar className={classes.toolbar}>
<IconButton
edge="start"
color="inherit"
aria-label="open drawer"
onClick={toggleDrawer}
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
<Typography
component="h1"
variant="h6"
color="inherit"
noWrap
className={classes.title}
>
Asynq Monitoring
</Typography>
</Toolbar>
</AppBar>
<div className={classes.mainContainer}>
<Drawer
variant="permanent"
classes={{
paper: clsx(
classes.drawerPaper,
!open && classes.drawerPaperClose
),
}}
open={open}
> >
<Snackbar <Toolbar className={classes.toolbar}>
anchorOrigin={{ vertical: "bottom", horizontal: "left" }} <IconButton
open={props.snackbar.isOpen} edge="start"
autoHideDuration={6000} color="inherit"
onClose={props.closeSnackbar} aria-label="open drawer"
TransitionComponent={SlideUpTransition} onClick={toggleDrawer}
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
<Typography
component="h1"
variant="h6"
color="inherit"
noWrap
className={classes.title}
>
Asynq Monitoring
</Typography>
</Toolbar>
</AppBar>
<div className={classes.mainContainer}>
<Drawer
variant="permanent"
classes={{
paper: clsx(
classes.drawerPaper,
!open && classes.drawerPaperClose
),
}}
open={open}
> >
<SnackbarContent <Snackbar
message={props.snackbar.message} anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
className={classes.snackbar} open={props.snackbar.isOpen}
action={ autoHideDuration={6000}
<IconButton onClose={props.closeSnackbar}
size="small" TransitionComponent={SlideUpTransition}
aria-label="close" >
color="inherit" <SnackbarContent
onClick={props.closeSnackbar} message={props.snackbar.message}
> className={classes.snackbar}
<CloseIcon action={
className={classes.snackbarCloseIcon} <IconButton
fontSize="small" size="small"
/> aria-label="close"
</IconButton> color="inherit"
} onClick={props.closeSnackbar}
/> >
</Snackbar> <CloseIcon
<div className={classes.appBarSpacer} /> className={classes.snackbarCloseIcon}
<div className={classes.sidebarContainer}> fontSize="small"
<List> />
<div> </IconButton>
<ListItemLink }
to={paths.HOME}
primary="Queues"
icon={<BarChartIcon />}
/>
<ListItemLink
to={paths.SERVERS}
primary="Servers"
icon={<DoubleArrowIcon />}
/>
<ListItemLink
to={paths.SCHEDULERS}
primary="Schedulers"
icon={<ScheduleIcon />}
/>
<ListItemLink
to={paths.REDIS}
primary="Redis"
icon={<LayersIcon />}
/>
</div>
</List>
<List>
<ListItemLink
to={paths.SETTINGS}
primary="Settings"
icon={<SettingsIcon />}
/> />
<ListItem </Snackbar>
button <div className={classes.appBarSpacer} />
component="a" <div className={classes.sidebarContainer}>
className={classes.listItem} <List>
href="https://github.com/hibiken/asynqmon/issues" <div>
target="_blank" <ListItemLink
> to={paths.HOME}
<ListItemIcon> primary="Queues"
<FeedbackIcon /> icon={<BarChartIcon />}
</ListItemIcon> />
<ListItemText primary="Send Feedback" /> <ListItemLink
</ListItem> to={paths.SERVERS}
</List> primary="Servers"
</div> icon={<DoubleArrowIcon />}
</Drawer> />
<main className={classes.content}> <ListItemLink
<div className={classes.contentWrapper}> to={paths.SCHEDULERS}
<Switch> primary="Schedulers"
<Route exact path={paths.QUEUE_DETAILS}> icon={<ScheduleIcon />}
<TasksView /> />
</Route> <ListItemLink
<Route exact path={paths.SCHEDULERS}> to={paths.REDIS}
<SchedulersView /> primary="Redis"
</Route> icon={<LayersIcon />}
<Route exact path={paths.SERVERS}> />
<ServersView /> </div>
</Route> </List>
<Route exact path={paths.REDIS}> <List>
<RedisInfoView /> <ListItemLink
</Route> to={paths.SETTINGS}
<Route exact path={paths.SETTINGS}> primary="Settings"
<SettingsView /> icon={<SettingsIcon />}
</Route> />
<Route exact path={paths.HOME}> <ListItem
<DashboardView /> button
</Route> component="a"
<Route path="*"> className={classes.listItem}
<PageNotFoundView /> href="https://github.com/hibiken/asynqmon/issues"
</Route> target="_blank"
</Switch> >
</div> <ListItemIcon>
</main> <FeedbackIcon />
</ListItemIcon>
<ListItemText primary="Send Feedback" />
</ListItem>
</List>
</div>
</Drawer>
<main className={classes.content}>
<div className={classes.contentWrapper}>
<Switch>
<Route exact path={paths.QUEUE_DETAILS}>
<TasksView />
</Route>
<Route exact path={paths.SCHEDULERS}>
<SchedulersView />
</Route>
<Route exact path={paths.SERVERS}>
<ServersView />
</Route>
<Route exact path={paths.REDIS}>
<RedisInfoView />
</Route>
<Route exact path={paths.SETTINGS}>
<SettingsView />
</Route>
<Route exact path={paths.HOME}>
<DashboardView />
</Route>
<Route path="*">
<PageNotFoundView />
</Route>
</Switch>
</div>
</main>
</div>
</div> </div>
</div> </Router>
</Router> </ThemeProvider>
); );
} }

View File

@ -1,13 +1,18 @@
// List of settings related action types. // List of settings related action types.
export const POLL_INTERVAL_CHANGE = "POLL_INTERVAL_CHANGE"; export const POLL_INTERVAL_CHANGE = 'POLL_INTERVAL_CHANGE';
export const TOGGLE_DARK_THEME = 'TOGGLE_DARK_THEME';
interface PollIntervalChangeAction { interface PollIntervalChangeAction {
type: typeof POLL_INTERVAL_CHANGE; type: typeof POLL_INTERVAL_CHANGE;
value: number; // new poll interval value in seconds value: number; // new poll interval value in seconds
} }
interface ToggleDarkThemeAction {
type: typeof TOGGLE_DARK_THEME;
}
// Union of all settings related action types. // Union of all settings related action types.
export type SettingsActionTypes = PollIntervalChangeAction; export type SettingsActionTypes = PollIntervalChangeAction | ToggleDarkThemeAction;
export function pollIntervalChange(value: number) { export function pollIntervalChange(value: number) {
return { return {
@ -15,3 +20,10 @@ export function pollIntervalChange(value: number) {
value, value,
}; };
} }
export function toggleDarkTheme() {
return {
type: TOGGLE_DARK_THEME
}
}

View File

@ -2,19 +2,15 @@ import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import CssBaseline from "@material-ui/core/CssBaseline"; import CssBaseline from "@material-ui/core/CssBaseline";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import { ThemeProvider } from "@material-ui/core/styles";
import App from "./App"; import App from "./App";
import store from "./store"; import store from "./store";
import theme from "./theme";
import * as serviceWorker from "./serviceWorker"; import * as serviceWorker from "./serviceWorker";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<CssBaseline /> <CssBaseline />
<Provider store={store}> <Provider store={store}>
<ThemeProvider theme={theme}> <App />
<App />
</ThemeProvider>
</Provider> </Provider>
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById("root")

View File

@ -1,23 +1,24 @@
import { import {POLL_INTERVAL_CHANGE, SettingsActionTypes, TOGGLE_DARK_THEME,} from '../actions/settingsActions';
POLL_INTERVAL_CHANGE,
SettingsActionTypes,
} from "../actions/settingsActions";
interface SettingsState { interface SettingsState {
pollInterval: number; pollInterval: number;
isDarkTheme: boolean;
} }
const initialState: SettingsState = { const initialState: SettingsState = {
pollInterval: 8, pollInterval: 8,
isDarkTheme: false,
}; };
function settingsReducer( function settingsReducer(
state = initialState, state = initialState, action: SettingsActionTypes): SettingsState {
action: SettingsActionTypes
): SettingsState {
switch (action.type) { switch (action.type) {
case POLL_INTERVAL_CHANGE: case POLL_INTERVAL_CHANGE:
return { ...state, pollInterval: action.value }; return {...state, pollInterval: action.value};
case TOGGLE_DARK_THEME:
return {
...state, isDarkTheme: !state.isDarkTheme
}
default: default:
return state; return state;
} }

View File

@ -1,18 +1,19 @@
import { createMuiTheme } from "@material-ui/core/styles"; import { createMuiTheme, Theme } from "@material-ui/core/styles";
// Got color palette from https://htmlcolors.com/palette/31/stripe export function makeTheme(isDarkTheme: boolean): Theme {
const theme = createMuiTheme({ return createMuiTheme({
palette: { // Got color palette from https://htmlcolors.com/palette/31/stripe
primary: { palette: {
main: "#4379FF", primary: {
main: "#4379FF",
},
secondary: {
main: "#97FBD1",
},
background: {
default: "#f5f7f9",
},
type: isDarkTheme ? "dark" : "light",
}, },
secondary: { });
main: "#97FBD1", }
},
background: {
default: "#f5f7f9",
},
},
});
export default theme;

View File

@ -5,8 +5,14 @@ import { makeStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import Slider from "@material-ui/core/Slider/Slider"; import Switch from "@material-ui/core/Switch";
import { pollIntervalChange } from "../actions/settingsActions"; import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormGroup from "@material-ui/core/FormGroup";
import Slider from "@material-ui/core/Slider";
import {
pollIntervalChange,
toggleDarkTheme,
} from "../actions/settingsActions";
import { AppState } from "../store"; import { AppState } from "../store";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -25,10 +31,11 @@ const useStyles = makeStyles((theme) => ({
function mapStateToProps(state: AppState) { function mapStateToProps(state: AppState) {
return { return {
pollInterval: state.settings.pollInterval, pollInterval: state.settings.pollInterval,
isDarkTheme: state.settings.isDarkTheme,
}; };
} }
const mapDispatchToProps = { pollIntervalChange }; const mapDispatchToProps = { pollIntervalChange, toggleDarkTheme };
const connector = connect(mapStateToProps, mapDispatchToProps); const connector = connect(mapStateToProps, mapDispatchToProps);
@ -46,6 +53,9 @@ function SettingsView(props: PropsFromRedux) {
props.pollIntervalChange(val as number); props.pollIntervalChange(val as number);
}; };
const handleThemeChange = (event: React.ChangeEvent<HTMLElement>) => {
props.toggleDarkTheme();
};
return ( return (
<Container maxWidth="lg" className={classes.container}> <Container maxWidth="lg" className={classes.container}>
<Grid container spacing={3}> <Grid container spacing={3}>
@ -75,6 +85,23 @@ function SettingsView(props: PropsFromRedux) {
/> />
</Paper> </Paper>
</Grid> </Grid>
<Grid item xs={5}>
<Paper>
<Typography color="textPrimary">theme switch</Typography>
<FormGroup row>
<FormControlLabel
control={
<Switch
checked={props.isDarkTheme}
onChange={handleThemeChange}
name="DarkTheme"
/>
}
label="🌛"
/>
</FormGroup>
</Paper>
</Grid>
</Grid> </Grid>
</Container> </Container>
); );