Make Menu Collapsable (#1025)
This commit is contained in:
@@ -15,21 +15,15 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
|
||||
interface IConsoleLogo {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const ConsoleLogo = ({ width = 120 }: IConsoleLogo) => {
|
||||
const ConsoleLogo = () => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={width}
|
||||
viewBox="0 0 121.755 29.822"
|
||||
>
|
||||
<defs>
|
||||
<style>{".prefix__a{fill:#fff}"}</style>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 121.755 29.822">
|
||||
<path
|
||||
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"
|
||||
|
||||
@@ -31,12 +31,17 @@ import {
|
||||
GLOBAL_SET_DISTRIBUTED_SETUP,
|
||||
} 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 = {
|
||||
loggedIn: false,
|
||||
operatorMode: false,
|
||||
session: "",
|
||||
userName: "",
|
||||
sidebarOpen: true,
|
||||
sidebarOpen: initSideBarOpen,
|
||||
serverNeedsRestart: false,
|
||||
serverIsLoading: false,
|
||||
loadingProgress: 100,
|
||||
@@ -70,6 +75,11 @@ export function systemReducer(
|
||||
operatorMode: action.operatorMode,
|
||||
};
|
||||
case MENU_OPEN:
|
||||
// persist preference to local storage
|
||||
localStorage.setItem(
|
||||
"sidebarOpen",
|
||||
JSON.stringify({ open: action.open })
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
sidebarOpen: action.open,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React from "react";
|
||||
import React, { Fragment } from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
interface IPageHeader {
|
||||
classes: any;
|
||||
label: any;
|
||||
actions?: any;
|
||||
}
|
||||
import { AppState } from "../../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { setMenuOpen, userLoggedIn } from "../../../../actions";
|
||||
import OperatorLogo from "../../../../icons/OperatorLogo";
|
||||
import ConsoleLogo from "../../../../icons/ConsoleLogo";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -37,9 +36,28 @@ const styles = (theme: Theme) =>
|
||||
marginTop: 16,
|
||||
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 (
|
||||
<Grid
|
||||
container
|
||||
@@ -47,6 +65,11 @@ const PageHeader = ({ classes, label, actions }: IPageHeader) => {
|
||||
justify={"space-between"}
|
||||
>
|
||||
<Grid item className={classes.label}>
|
||||
{!sidebarOpen && (
|
||||
<div className={classes.logo}>
|
||||
{operatorMode ? <OperatorLogo /> : <ConsoleLogo />}
|
||||
</div>
|
||||
)}
|
||||
<Typography variant="h4" className={classes.labelStyle}>
|
||||
{label}
|
||||
</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));
|
||||
|
||||
@@ -384,20 +384,7 @@ const Console = ({
|
||||
{session.status === "ok" ? (
|
||||
<div className={classes.root}>
|
||||
<CssBaseline />
|
||||
{!hideMenu && (
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
classes={{
|
||||
paper: clsx(
|
||||
classes.drawerPaper,
|
||||
!open && classes.drawerPaperClose
|
||||
),
|
||||
}}
|
||||
open={open}
|
||||
>
|
||||
<Menu pages={session.pages} />
|
||||
</Drawer>
|
||||
)}
|
||||
{!hideMenu && <Menu pages={session.pages} />}
|
||||
|
||||
<main className={classes.content}>
|
||||
{needsRestart && (
|
||||
|
||||
@@ -14,19 +14,25 @@
|
||||
// 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/>.
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
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 ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import List from "@material-ui/core/List";
|
||||
import { AppState } from "../../../store";
|
||||
import { userLoggedIn } from "../../../actions";
|
||||
import { setMenuOpen, userLoggedIn } from "../../../actions";
|
||||
import { menuGroups } from "./utils";
|
||||
import { IMenuItem, IMenuProps } from "./types";
|
||||
import { IMenuItem } from "./types";
|
||||
import {
|
||||
BucketsIcon,
|
||||
DashboardIcon,
|
||||
@@ -52,6 +58,12 @@ import LogsIcon from "../../../icons/LogsIcon";
|
||||
import SettingsIcon from "../../../icons/SettingsIcon";
|
||||
import StorageIcon from "../../../icons/StorageIcon";
|
||||
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) =>
|
||||
createStyles({
|
||||
@@ -59,9 +71,39 @@ const styles = (theme: Theme) =>
|
||||
paddingTop: 25,
|
||||
marginBottom: 30,
|
||||
paddingLeft: 45,
|
||||
transition: theme.transitions.create("paddingLeft", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
"& img": {
|
||||
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: {
|
||||
"& .active": {
|
||||
@@ -147,6 +189,80 @@ const styles = (theme: Theme) =>
|
||||
selectorArrowOpen: {
|
||||
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
|
||||
@@ -161,15 +277,25 @@ const menuStateBuilder = () => {
|
||||
return elements;
|
||||
};
|
||||
|
||||
interface IMenuProps {
|
||||
classes: any;
|
||||
userLoggedIn: typeof userLoggedIn;
|
||||
pages: string[];
|
||||
operatorMode: boolean;
|
||||
distributedSetup: boolean;
|
||||
sidebarOpen: boolean;
|
||||
setMenuOpen: typeof setMenuOpen;
|
||||
}
|
||||
|
||||
const Menu = ({
|
||||
userLoggedIn,
|
||||
classes,
|
||||
pages,
|
||||
operatorMode,
|
||||
distributedSetup,
|
||||
sidebarOpen,
|
||||
setMenuOpen,
|
||||
}: IMenuProps) => {
|
||||
const [menuOpen, setMenuOpen] = useState<any>(menuStateBuilder());
|
||||
|
||||
const logout = () => {
|
||||
const deleteSession = () => {
|
||||
clearSession();
|
||||
@@ -385,91 +511,130 @@ const Menu = ({
|
||||
item.fsHidden !== false
|
||||
);
|
||||
|
||||
const setMenuCollapse = (menuClicked: string) => {
|
||||
let newMenu: any = { ...menuOpen };
|
||||
const handleDrawerOpen = () => {
|
||||
setMenuOpen(true);
|
||||
};
|
||||
|
||||
newMenu[menuClicked] = !newMenu[menuClicked];
|
||||
|
||||
setMenuOpen(newMenu);
|
||||
const handleDrawerClose = () => {
|
||||
setMenuOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={classes.logo}>
|
||||
{operatorMode ? <OperatorLogo /> : <ConsoleLogo />}
|
||||
</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 && <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>
|
||||
);
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
className={clsx(classes.drawer, {
|
||||
[classes.drawerOpen]: sidebarOpen,
|
||||
[classes.drawerClose]: !sidebarOpen,
|
||||
})}
|
||||
<Divider className={classes.menuDivider} />
|
||||
<ListItem button onClick={logout}>
|
||||
<ListItemIcon>
|
||||
<LogoutIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Logout" />
|
||||
</ListItem>
|
||||
</List>
|
||||
classes={{
|
||||
paper: clsx({
|
||||
[classes.drawerOpen]: sidebarOpen,
|
||||
[classes.drawerClose]: !sidebarOpen,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
open: state.system.loggedIn,
|
||||
sidebarOpen: state.system.sidebarOpen,
|
||||
operatorMode: state.system.operatorMode,
|
||||
distributedSetup: state.system.distributedSetup,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { userLoggedIn });
|
||||
const connector = connect(mapState, { userLoggedIn, setMenuOpen });
|
||||
|
||||
export default connector(withStyles(styles)(Menu));
|
||||
|
||||
@@ -16,14 +16,6 @@
|
||||
|
||||
import { userLoggedIn } from "../../../actions";
|
||||
|
||||
export interface IMenuProps {
|
||||
classes: any;
|
||||
userLoggedIn: typeof userLoggedIn;
|
||||
pages: string[];
|
||||
operatorMode: boolean;
|
||||
distributedSetup: boolean;
|
||||
}
|
||||
|
||||
export interface IMenuItem {
|
||||
group: string;
|
||||
type: string;
|
||||
|
||||
Reference in New Issue
Block a user