Make Menu Collapsable (#1025)

This commit is contained in:
Daniel Valdivia
2021-09-10 18:33:50 -07:00
committed by GitHub
parent e104c4a48e
commit 67b0261b0b
6 changed files with 296 additions and 118 deletions

View File

@@ -15,21 +15,15 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react"; import React from "react";
import { SvgIcon } from "@material-ui/core";
interface IConsoleLogo { interface IConsoleLogo {
width?: number; width?: number;
} }
const ConsoleLogo = ({ width = 120 }: IConsoleLogo) => { const ConsoleLogo = () => {
return ( return (
<svg <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 121.755 29.822">
xmlns="http://www.w3.org/2000/svg"
width={width}
viewBox="0 0 121.755 29.822"
>
<defs>
<style>{".prefix__a{fill:#fff}"}</style>
</defs>
<path <path
className="prefix__a" className="prefix__a"
d="M15.647 15.935l-1.772 1.194a6.088 6.088 0 00-5.135-2.652 6.348 6.348 0 00-6.522 6.654 6.348 6.348 0 006.522 6.654 6.031 6.031 0 005.124-2.64l1.735 1.266a8.126 8.126 0 01-6.859 3.411A8.422 8.422 0 010 21.131a8.422 8.422 0 018.74-8.691 7.963 7.963 0 016.907 3.495zM35.641 21.131a8.422 8.422 0 01-8.74 8.691 8.422 8.422 0 01-8.739-8.691 8.421 8.421 0 018.739-8.691 8.422 8.422 0 018.74 8.691zm-15.261 0a6.347 6.347 0 006.521 6.654 6.347 6.347 0 006.521-6.654 6.347 6.347 0 00-6.521-6.654 6.347 6.347 0 00-6.521 6.654zM53.729 29.581h-1.917l-10.2-13.26-.012 13.248h-2.122V12.681h1.917l10.21 13.26V12.693h2.122zM69.551 16.02a8.835 8.835 0 00-5-1.555c-2.471 0-4.231 1.109-4.231 2.929 0 1.531 1.29 2.315 3.821 2.628l1.484.181c2.856.35 5.3 1.507 5.3 4.484 0 3.364-3.05 5.123-6.7 5.123a10.935 10.935 0 01-6.654-2.194l1.157-1.687a9.018 9.018 0 005.5 1.868c2.519 0 4.5-1.025 4.5-2.929 0-1.567-1.41-2.314-4.038-2.64l-1.567-.193c-2.784-.337-5-1.627-5-4.508 0-3.255 2.893-5.075 6.449-5.075a10.336 10.336 0 016.076 1.844zM91.268 21.131a8.422 8.422 0 01-8.74 8.691 8.422 8.422 0 01-8.739-8.691 8.421 8.421 0 018.739-8.691 8.422 8.422 0 018.74 8.691zm-15.261 0a6.348 6.348 0 006.521 6.654 6.347 6.347 0 006.521-6.654 6.347 6.347 0 00-6.521-6.654 6.348 6.348 0 00-6.521 6.654zM106.897 29.569h-11.79V12.693h2.122v14.863h9.668zM121.76 29.569h-11.982V12.693h11.862v1.988h-9.74v5.389h9.427v2H111.9v5.509h9.86zM14.9.167h2.576v7.547H14.9zM11.801.238l-5.23 3.194a.229.229 0 01-.242 0L1.099.238a.726.726 0 00-.374-.1H.719a.717.717 0 00-.717.717v6.864h2.574V4.462a.258.258 0 01.392-.22l2.931 1.793a.919.919 0 00.944.009L9.935 4.23a.258.258 0 01.388.222v3.267h2.575V.855a.717.717 0 00-.717-.717h-.006a.723.723 0 00-.374.1zM30.348.165h-2.613v3.463a.258.258 0 01-.379.228L20.585.249a.723.723 0 00-.337-.084.717.717 0 00-.717.717v6.832h2.592V4.306a.258.258 0 01.379-.227l6.8 3.606a.714.714 0 00.336.083.716.716 0 00.717-.717V.165zM32.439 7.712V.165h1.2v7.547zM40.536 7.878c-3.189 0-5.451-1.513-5.451-3.939S37.361 0 40.536 0s5.466 1.513 5.466 3.939-2.236 3.939-5.466 3.939zm0-6.87c-2.371 0-4.2 1.036-4.2 2.931s1.826 2.93 4.2 2.93 4.212-1.022 4.212-2.93-1.84-2.931-4.212-2.931z" d="M15.647 15.935l-1.772 1.194a6.088 6.088 0 00-5.135-2.652 6.348 6.348 0 00-6.522 6.654 6.348 6.348 0 006.522 6.654 6.031 6.031 0 005.124-2.64l1.735 1.266a8.126 8.126 0 01-6.859 3.411A8.422 8.422 0 010 21.131a8.422 8.422 0 018.74-8.691 7.963 7.963 0 016.907 3.495zM35.641 21.131a8.422 8.422 0 01-8.74 8.691 8.422 8.422 0 01-8.739-8.691 8.421 8.421 0 018.739-8.691 8.422 8.422 0 018.74 8.691zm-15.261 0a6.347 6.347 0 006.521 6.654 6.347 6.347 0 006.521-6.654 6.347 6.347 0 00-6.521-6.654 6.347 6.347 0 00-6.521 6.654zM53.729 29.581h-1.917l-10.2-13.26-.012 13.248h-2.122V12.681h1.917l10.21 13.26V12.693h2.122zM69.551 16.02a8.835 8.835 0 00-5-1.555c-2.471 0-4.231 1.109-4.231 2.929 0 1.531 1.29 2.315 3.821 2.628l1.484.181c2.856.35 5.3 1.507 5.3 4.484 0 3.364-3.05 5.123-6.7 5.123a10.935 10.935 0 01-6.654-2.194l1.157-1.687a9.018 9.018 0 005.5 1.868c2.519 0 4.5-1.025 4.5-2.929 0-1.567-1.41-2.314-4.038-2.64l-1.567-.193c-2.784-.337-5-1.627-5-4.508 0-3.255 2.893-5.075 6.449-5.075a10.336 10.336 0 016.076 1.844zM91.268 21.131a8.422 8.422 0 01-8.74 8.691 8.422 8.422 0 01-8.739-8.691 8.421 8.421 0 018.739-8.691 8.422 8.422 0 018.74 8.691zm-15.261 0a6.348 6.348 0 006.521 6.654 6.347 6.347 0 006.521-6.654 6.347 6.347 0 00-6.521-6.654 6.348 6.348 0 00-6.521 6.654zM106.897 29.569h-11.79V12.693h2.122v14.863h9.668zM121.76 29.569h-11.982V12.693h11.862v1.988h-9.74v5.389h9.427v2H111.9v5.509h9.86zM14.9.167h2.576v7.547H14.9zM11.801.238l-5.23 3.194a.229.229 0 01-.242 0L1.099.238a.726.726 0 00-.374-.1H.719a.717.717 0 00-.717.717v6.864h2.574V4.462a.258.258 0 01.392-.22l2.931 1.793a.919.919 0 00.944.009L9.935 4.23a.258.258 0 01.388.222v3.267h2.575V.855a.717.717 0 00-.717-.717h-.006a.723.723 0 00-.374.1zM30.348.165h-2.613v3.463a.258.258 0 01-.379.228L20.585.249a.723.723 0 00-.337-.084.717.717 0 00-.717.717v6.832h2.592V4.306a.258.258 0 01.379-.227l6.8 3.606a.714.714 0 00.336.083.716.716 0 00.717-.717V.165zM32.439 7.712V.165h1.2v7.547zM40.536 7.878c-3.189 0-5.451-1.513-5.451-3.939S37.361 0 40.536 0s5.466 1.513 5.466 3.939-2.236 3.939-5.466 3.939zm0-6.87c-2.371 0-4.2 1.036-4.2 2.931s1.826 2.93 4.2 2.93 4.212-1.022 4.212-2.93-1.84-2.931-4.212-2.931z"

View File

@@ -31,12 +31,17 @@ import {
GLOBAL_SET_DISTRIBUTED_SETUP, GLOBAL_SET_DISTRIBUTED_SETUP,
} from "./types"; } from "./types";
// determine whether we have the sidebar state stored on localstorage
const initSideBarOpen = localStorage.getItem("sidebarOpen")
? JSON.parse(localStorage.getItem("sidebarOpen")!)["open"]
: true;
const initialState: SystemState = { const initialState: SystemState = {
loggedIn: false, loggedIn: false,
operatorMode: false, operatorMode: false,
session: "", session: "",
userName: "", userName: "",
sidebarOpen: true, sidebarOpen: initSideBarOpen,
serverNeedsRestart: false, serverNeedsRestart: false,
serverIsLoading: false, serverIsLoading: false,
loadingProgress: 100, loadingProgress: 100,
@@ -70,6 +75,11 @@ export function systemReducer(
operatorMode: action.operatorMode, operatorMode: action.operatorMode,
}; };
case MENU_OPEN: case MENU_OPEN:
// persist preference to local storage
localStorage.setItem(
"sidebarOpen",
JSON.stringify({ open: action.open })
);
return { return {
...state, ...state,
sidebarOpen: action.open, sidebarOpen: action.open,

View File

@@ -1,13 +1,12 @@
import React from "react"; import React, { Fragment } from "react";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import { AppState } from "../../../../store";
interface IPageHeader { import { connect } from "react-redux";
classes: any; import { setMenuOpen, userLoggedIn } from "../../../../actions";
label: any; import OperatorLogo from "../../../../icons/OperatorLogo";
actions?: any; import ConsoleLogo from "../../../../icons/ConsoleLogo";
}
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@@ -37,9 +36,28 @@ const styles = (theme: Theme) =>
marginTop: 16, marginTop: 16,
marginRight: 8, marginRight: 8,
}, },
logo: {
marginLeft: 34,
fill: theme.palette.primary.main,
width: 120,
},
}); });
const PageHeader = ({ classes, label, actions }: IPageHeader) => { interface IPageHeader {
classes: any;
sidebarOpen?: boolean;
operatorMode?: boolean;
label: any;
actions?: any;
}
const PageHeader = ({
classes,
label,
actions,
sidebarOpen,
operatorMode,
}: IPageHeader) => {
return ( return (
<Grid <Grid
container container
@@ -47,6 +65,11 @@ const PageHeader = ({ classes, label, actions }: IPageHeader) => {
justify={"space-between"} justify={"space-between"}
> >
<Grid item className={classes.label}> <Grid item className={classes.label}>
{!sidebarOpen && (
<div className={classes.logo}>
{operatorMode ? <OperatorLogo /> : <ConsoleLogo />}
</div>
)}
<Typography variant="h4" className={classes.labelStyle}> <Typography variant="h4" className={classes.labelStyle}>
{label} {label}
</Typography> </Typography>
@@ -60,4 +83,11 @@ const PageHeader = ({ classes, label, actions }: IPageHeader) => {
); );
}; };
export default withStyles(styles)(PageHeader); const mapState = (state: AppState) => ({
sidebarOpen: state.system.sidebarOpen,
operatorMode: state.system.operatorMode,
});
const connector = connect(mapState, null);
export default connector(withStyles(styles)(PageHeader));

View File

@@ -384,20 +384,7 @@ const Console = ({
{session.status === "ok" ? ( {session.status === "ok" ? (
<div className={classes.root}> <div className={classes.root}>
<CssBaseline /> <CssBaseline />
{!hideMenu && ( {!hideMenu && <Menu pages={session.pages} />}
<Drawer
variant="permanent"
classes={{
paper: clsx(
classes.drawerPaper,
!open && classes.drawerPaperClose
),
}}
open={open}
>
<Menu pages={session.pages} />
</Drawer>
)}
<main className={classes.content}> <main className={classes.content}>
{needsRestart && ( {needsRestart && (

View File

@@ -14,19 +14,25 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useState } from "react"; import React, { Fragment, useState } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { Divider, withStyles } from "@material-ui/core"; import {
Divider,
Drawer,
IconButton,
Tooltip,
withStyles,
} from "@material-ui/core";
import { createStyles, Theme } from "@material-ui/core/styles"; import { createStyles, Theme } from "@material-ui/core/styles";
import ListItem from "@material-ui/core/ListItem"; import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon"; import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText"; import ListItemText from "@material-ui/core/ListItemText";
import List from "@material-ui/core/List"; import List from "@material-ui/core/List";
import { AppState } from "../../../store"; import { AppState } from "../../../store";
import { userLoggedIn } from "../../../actions"; import { setMenuOpen, userLoggedIn } from "../../../actions";
import { menuGroups } from "./utils"; import { menuGroups } from "./utils";
import { IMenuItem, IMenuProps } from "./types"; import { IMenuItem } from "./types";
import { import {
BucketsIcon, BucketsIcon,
DashboardIcon, DashboardIcon,
@@ -52,6 +58,12 @@ import LogsIcon from "../../../icons/LogsIcon";
import SettingsIcon from "../../../icons/SettingsIcon"; import SettingsIcon from "../../../icons/SettingsIcon";
import StorageIcon from "../../../icons/StorageIcon"; import StorageIcon from "../../../icons/StorageIcon";
import TenantsOutlinedIcon from "../../../icons/TenantsOutlineIcon"; import TenantsOutlinedIcon from "../../../icons/TenantsOutlineIcon";
import MenuIcon from "@material-ui/icons/Menu";
import clsx from "clsx";
import { ChevronLeft } from "@material-ui/icons";
const drawerWidth = 245;
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@@ -59,9 +71,39 @@ const styles = (theme: Theme) =>
paddingTop: 25, paddingTop: 25,
marginBottom: 30, marginBottom: 30,
paddingLeft: 45, paddingLeft: 45,
transition: theme.transitions.create("paddingLeft", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
"& img": { "& img": {
width: 120, width: 120,
}, },
"& .MuiIconButton-root": {
color: "#ffffff",
float: "right",
},
},
logoClosed: {
paddingTop: 25,
marginBottom: 30,
paddingLeft: 34,
transition: theme.transitions.create("paddingLeft", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
"& .MuiIconButton-root": {
color: "#ffffff",
},
},
logoSvg: {
width: 40,
},
logoSvgClosed: {
width: 0,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
}, },
menuList: { menuList: {
"& .active": { "& .active": {
@@ -147,6 +189,80 @@ const styles = (theme: Theme) =>
selectorArrowOpen: { selectorArrowOpen: {
transform: "rotateZ(180deg)", transform: "rotateZ(180deg)",
}, },
//new
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginRight: 36,
},
hide: {
display: "none",
},
drawer: {
width: drawerWidth,
flexShrink: 0,
whiteSpace: "nowrap",
background:
"transparent linear-gradient(90deg, #073052 0%, #081C42 100%) 0% 0% no-repeat padding-box !important",
boxShadow: "0px 3px 7px #00000014",
"& .MuiPaper-root": {
backgroundColor: "inherit",
},
},
drawerOpen: {
width: drawerWidth,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerClose: {
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: "hidden",
width: 115,
[theme.breakpoints.up("sm")]: {
width: 115,
},
"& .logo": {
background: "red",
},
"& .MuiListItem-root": {
padding: "2px 0 2px 16px",
marginLeft: 36,
height: 50,
width: "48px",
transition: theme.transitions.create("marginLeft", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
"& .MuiListItemText-root": {
display: "none",
},
},
},
logoIcon: {
float: "left",
"& svg": {
fill: "white",
width: 120,
},
},
}); });
// Menu State builder for groups // Menu State builder for groups
@@ -161,15 +277,25 @@ const menuStateBuilder = () => {
return elements; return elements;
}; };
interface IMenuProps {
classes: any;
userLoggedIn: typeof userLoggedIn;
pages: string[];
operatorMode: boolean;
distributedSetup: boolean;
sidebarOpen: boolean;
setMenuOpen: typeof setMenuOpen;
}
const Menu = ({ const Menu = ({
userLoggedIn, userLoggedIn,
classes, classes,
pages, pages,
operatorMode, operatorMode,
distributedSetup, distributedSetup,
sidebarOpen,
setMenuOpen,
}: IMenuProps) => { }: IMenuProps) => {
const [menuOpen, setMenuOpen] = useState<any>(menuStateBuilder());
const logout = () => { const logout = () => {
const deleteSession = () => { const deleteSession = () => {
clearSession(); clearSession();
@@ -385,91 +511,130 @@ const Menu = ({
item.fsHidden !== false item.fsHidden !== false
); );
const setMenuCollapse = (menuClicked: string) => { const handleDrawerOpen = () => {
let newMenu: any = { ...menuOpen }; setMenuOpen(true);
};
newMenu[menuClicked] = !newMenu[menuClicked]; const handleDrawerClose = () => {
setMenuOpen(false);
setMenuOpen(newMenu);
}; };
return ( return (
<React.Fragment> <React.Fragment>
<div className={classes.logo}> <Drawer
{operatorMode ? <OperatorLogo /> : <ConsoleLogo />} variant="permanent"
</div> className={clsx(classes.drawer, {
<List className={classes.menuList}> [classes.drawerOpen]: sidebarOpen,
{menuGroups.map((groupMember, index) => { [classes.drawerClose]: !sidebarOpen,
const filterByGroup = (allowedItems || []).filter(
(item: any) => item.group === groupMember.group
);
const countableElements = filterByGroup.filter(
(menuItem: any) => menuItem.type !== "title"
);
if (countableElements.length === 0) {
return null;
}
return (
<React.Fragment key={`menuElem-${index.toString()}`}>
<Divider className={classes.menuDivider} />
{filterByGroup.map((page: IMenuItem) => {
switch (page.type) {
case "item": {
return (
<ListItem
key={page.to}
button
onClick={page.onClick}
component={page.component}
to={page.to}
className={
page.extraMargin ? classes.extraMargin : null
}
>
{page.icon && <ListItemIcon>{page.icon}</ListItemIcon>}
{page.name && <ListItemText primary={page.name} />}
</ListItem>
);
}
case "title": {
return (
<ListItem
key={page.name}
component={page.component}
className={classes.subTitleMenu}
>
{page.name}
</ListItem>
);
}
default:
return null;
}
})}
</React.Fragment>
);
})} })}
<Divider className={classes.menuDivider} /> classes={{
<ListItem button onClick={logout}> paper: clsx({
<ListItemIcon> [classes.drawerOpen]: sidebarOpen,
<LogoutIcon /> [classes.drawerClose]: !sidebarOpen,
</ListItemIcon> }),
<ListItemText primary="Logout" /> }}
</ListItem> >
</List> <div
className={clsx({
[classes.logo]: sidebarOpen,
[classes.logoClosed]: !sidebarOpen,
})}
>
{sidebarOpen && (
<span className={classes.logoIcon}>
{operatorMode ? <OperatorLogo /> : <ConsoleLogo />}
</span>
)}
<IconButton
onClick={() => {
if (sidebarOpen) {
setMenuOpen(false);
} else {
setMenuOpen(true);
}
}}
>
{sidebarOpen ? <ChevronLeft /> : <MenuIcon />}
</IconButton>
</div>
<List className={classes.menuList}>
{menuGroups.map((groupMember, index) => {
const filterByGroup = (allowedItems || []).filter(
(item: any) => item.group === groupMember.group
);
const countableElements = filterByGroup.filter(
(menuItem: any) => menuItem.type !== "title"
);
if (countableElements.length === 0) {
return null;
}
return (
<React.Fragment key={`menuElem-${index.toString()}`}>
<Divider className={classes.menuDivider} />
{filterByGroup.map((page: IMenuItem) => {
switch (page.type) {
case "item": {
return (
<ListItem
key={page.to}
button
onClick={page.onClick}
component={page.component}
to={page.to}
className={
page.extraMargin ? classes.extraMargin : null
}
>
{page.icon && (
<Tooltip title={page.name}>
<ListItemIcon>{page.icon}</ListItemIcon>
</Tooltip>
)}
{page.name && <ListItemText primary={page.name} />}
</ListItem>
);
}
case "title": {
return (
<ListItem
key={page.name}
component={page.component}
className={classes.subTitleMenu}
>
{page.name}
</ListItem>
);
}
default:
return null;
}
})}
</React.Fragment>
);
})}
<Divider className={classes.menuDivider} />
<ListItem button onClick={logout}>
<ListItemIcon>
<LogoutIcon />
</ListItemIcon>
<ListItemText primary="Logout" />
</ListItem>
</List>
</Drawer>
</React.Fragment> </React.Fragment>
); );
}; };
const mapState = (state: AppState) => ({ const mapState = (state: AppState) => ({
open: state.system.loggedIn, sidebarOpen: state.system.sidebarOpen,
operatorMode: state.system.operatorMode, operatorMode: state.system.operatorMode,
distributedSetup: state.system.distributedSetup, distributedSetup: state.system.distributedSetup,
}); });
const connector = connect(mapState, { userLoggedIn }); const connector = connect(mapState, { userLoggedIn, setMenuOpen });
export default connector(withStyles(styles)(Menu)); export default connector(withStyles(styles)(Menu));

View File

@@ -16,14 +16,6 @@
import { userLoggedIn } from "../../../actions"; import { userLoggedIn } from "../../../actions";
export interface IMenuProps {
classes: any;
userLoggedIn: typeof userLoggedIn;
pages: string[];
operatorMode: boolean;
distributedSetup: boolean;
}
export interface IMenuItem { export interface IMenuItem {
group: string; group: string;
type: string; type: string;