Skip to content

Commit

Permalink
Improve AppBar (#2190)
Browse files Browse the repository at this point in the history
Remove hard-coded grid size specifications to allow buttons to shift and fit more dynamically.
Restore small Combine icon for narrow windows, as it's the only way to get back to the ProjectScreen without logging out and back in again.
Fix max height of app-bar to prevent vertical stretching when "DATA ENTRY" or "DATA CLEANUP" overflow to third line in localization.
To save space on narrow windows, move Statistics button from large "DATA STATISTICS" nav button to small stats-icon button next to project settings.
  • Loading branch information
imnasnainaec authored May 24, 2023
1 parent 73b92db commit 3178d80
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 157 deletions.
8 changes: 4 additions & 4 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@
"wordInDatabase": "This word is already in the database"
},
"appBar": {
"dataEntry": "data entry",
"dataCleanup": "data cleanup",
"statistics": "data statistics"
"dataEntry": "Data Entry",
"dataCleanup": "Data Cleanup",
"statistics": "Data Statistics",
"projectSettings": "Project Settings"
},
"login": {
"title": "Log In",
Expand Down Expand Up @@ -77,7 +78,6 @@
},
"userMenu": {
"siteSettings": "Site Settings",
"projectSettings": "Project Settings",
"userSettings": "User Settings",
"logout": "Log Out",
"userGuide": "User Guide"
Expand Down
32 changes: 15 additions & 17 deletions src/components/AppBar/AppBarComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
import { AppBar, Grid, Hidden, Toolbar } from "@mui/material";
import { AppBar, Grid, Toolbar } from "@mui/material";
import { ReactElement, useEffect, useState } from "react";
import { useLocation } from "react-router-dom";

import { getProjectId } from "backend/localStorage";
import { getBasePath, Path } from "browserHistory";
import Logo from "components/AppBar/Logo";
import NavigationButtons from "components/AppBar/NavigationButtons";
import ProjectNameButton from "components/AppBar/ProjectNameButton";
import ProjectButtons from "components/AppBar/ProjectButtons";
import UserMenu from "components/AppBar/UserMenu";
import { topBarHeight } from "components/LandingPage/TopBar";
import DownloadButton from "components/ProjectExport/DownloadButton";
import theme from "types/theme";

export const appBarHeight = 64;

/** An app bar shown at the top of all logged in pages */
export default function AppBarComponent(): ReactElement {
const location = useLocation();
const [currentTab, setCurrentTab] = useState<Path>(Path.ProjScreen);

useEffect(() => setCurrentTab(getBasePath(location.pathname)), [location]);

return (
<div className="NavigationBar" style={{ marginBottom: topBarHeight }}>
<AppBar position="fixed" style={{ zIndex: theme.zIndex.drawer + 1 }}>
<AppBar
position="fixed"
style={{ maxHeight: appBarHeight, zIndex: theme.zIndex.drawer + 1 }}
>
<Toolbar>
<Grid
container
justifyContent="space-between"
spacing={2}
alignItems="center"
>
<Grid item sm={7} md={6} lg={5}>
<Hidden smDown>
<Logo />
</Hidden>
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Logo />
{!!getProjectId() && (
<NavigationButtons currentTab={currentTab} />
)}
</Grid>
<Grid item xs={1} sm={2} md={3} lg={4}>
{!!getProjectId() && (
<ProjectNameButton currentTab={currentTab} />
)}
<Grid item>
{!!getProjectId() && <ProjectButtons currentTab={currentTab} />}
<DownloadButton colorSecondary />
</Grid>
<Grid item>
Expand Down
18 changes: 11 additions & 7 deletions src/components/AppBar/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import smallLogo from "resources/CombineSmallLogoV1.png";
/** A button that redirects to the home page */
export default function Logo(): ReactElement {
return (
<Button onClick={() => history.push(Path.ProjScreen)} id="logo-button">
<Hidden lgDown>
<img src={logo} height="50" alt="Logo" />
<Button
id="logo-button"
onClick={() => history.push(Path.ProjScreen)}
style={{ minWidth: 0, padding: 0 }}
>
<Hidden mdDown>
<img src={logo} height="45" alt="Logo" />
</Hidden>
<Hidden lgUp mdDown>
<img src={logo} height="40" alt="Logo" />
<Hidden smDown mdUp>
<img src={smallLogo} height="30" alt="Logo" />
</Hidden>
<Hidden mdUp>
<img src={smallLogo} height="35" alt="Logo" />
<Hidden smUp>
<img src={smallLogo} height="15" alt="Logo" />
</Hidden>
</Button>
);
Expand Down
66 changes: 12 additions & 54 deletions src/components/AppBar/NavigationButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,47 @@
import { Button } from "@mui/material";
import React, { ReactElement, useState } from "react";
import { ReactElement } from "react";
import { useTranslation } from "react-i18next";

import { Permission } from "api";
import * as backend from "backend";
import * as LocalStorage from "backend/localStorage";
import history, { Path } from "browserHistory";
import { openTreeAction } from "components/TreeView/TreeViewActions";
import { useAppDispatch } from "types/hooks";
import { appBarHeight } from "components/AppBar/AppBarComponent";
import { tabColor } from "types/theme";

interface NavigationButtonsProps {
currentTab: Path;
}

export async function getIsAdminOrOwner(): Promise<boolean> {
const user = LocalStorage.getCurrentUser();
if (user?.isAdmin) {
return true;
} else {
const projectId = LocalStorage.getProjectId();
const userRoleID = user?.projectRoles[projectId];
if (userRoleID) {
return backend.getUserRole(userRoleID).then((role) => {
return role.permissions.includes(Permission.Owner);
});
}
}
return false;
}

/** A button that redirects to the home page */
export default function NavigationButtons(
props: NavigationButtonsProps
): ReactElement {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const [isAdminOrOwner, setIsAdminOrOwner] = useState<boolean>(false);

getIsAdminOrOwner().then(setIsAdminOrOwner);

return (
<React.Fragment>
<>
<Button
id="data-entry"
onClick={() => {
dispatch(openTreeAction());
history.push(Path.DataEntry);
}}
id={"data-entry"}
onClick={() => history.push(Path.DataEntry)}
color="inherit"
style={{
width: "fit-content",
background: tabColor(props.currentTab, Path.DataEntry),
maxHeight: appBarHeight,
width: "min-content",
}}
>
{t("appBar.dataEntry")}
</Button>
<Button
id="goals"
onClick={() => {
history.push(Path.Goals);
}}
id={"data-cleanup"}
onClick={() => history.push(Path.Goals)}
color="inherit"
style={{
width: "fit-content",
background: tabColor(props.currentTab, Path.Goals),
maxHeight: appBarHeight,
width: "min-content",
}}
>
{t("appBar.dataCleanup")}
</Button>
{isAdminOrOwner && (
<Button
id="statistics"
onClick={() => {
history.push(Path.Statistics);
}}
color="inherit"
style={{
width: "min-content",
background: tabColor(props.currentTab, Path.Statistics),
}}
>
{t("appBar.statistics")}
</Button>
)}
</React.Fragment>
</>
);
}
85 changes: 85 additions & 0 deletions src/components/AppBar/ProjectButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { BarChart, Settings } from "@mui/icons-material";
import { Button, Hidden, Tooltip, Typography } from "@mui/material";
import { ReactElement, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";

import { Permission } from "api/models";
import { getUserRole } from "backend";
import { getCurrentUser, getProjectId } from "backend/localStorage";
import history, { Path } from "browserHistory";
import { StoreState } from "types";
import { tabColor } from "types/theme";

interface ProjectButtonsProps {
currentTab: Path;
}

export async function getIsAdminOrOwner(): Promise<boolean> {
const user = getCurrentUser();
if (!user) {
return false;
}
if (user.isAdmin) {
return true;
}
const userRoleID = user.projectRoles[getProjectId()];
if (userRoleID) {
const role = await getUserRole(userRoleID);
return role.permissions.includes(Permission.Owner);
}
return false;
}

/** A button that redirects to the project settings */
export default function ProjectButtons(
props: ProjectButtonsProps
): ReactElement {
const projectName = useSelector(
(state: StoreState) => state.currentProjectState.project.name
);
const [isAdminOrOwner, setIsAdminOrOwner] = useState<boolean>(false);
const { t } = useTranslation();

useEffect(() => {
getIsAdminOrOwner().then(setIsAdminOrOwner);
}, [setIsAdminOrOwner]);

return (
<>
{isAdminOrOwner && (
<Tooltip title={t("appBar.statistics")}>
<Button
id="project-statistics"
onClick={() => history.push(Path.Statistics)}
color="inherit"
style={{
background: tabColor(props.currentTab, Path.Statistics),
minWidth: 0,
}}
>
<BarChart />
</Button>
</Tooltip>
)}
<Tooltip title={t("appBar.projectSettings")}>
<Button
id="project-settings"
onClick={() => history.push(Path.ProjSettings)}
color="inherit"
style={{
background: tabColor(props.currentTab, Path.ProjSettings),
minWidth: 0,
}}
>
<Settings />
</Button>
</Tooltip>
<Hidden smDown>
<Typography display="inline" style={{ margin: 5 }}>
{projectName}
</Typography>
</Hidden>
</>
);
}
38 changes: 0 additions & 38 deletions src/components/AppBar/ProjectNameButton.tsx

This file was deleted.

25 changes: 20 additions & 5 deletions src/components/AppBar/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import {
Person,
SettingsApplications,
} from "@mui/icons-material";
import { Avatar, Button, Hidden, Menu, MenuItem } from "@mui/material";
import {
Avatar,
Button,
Hidden,
Menu,
MenuItem,
Typography,
} from "@mui/material";
import React, { ReactElement, useState } from "react";
import { useTranslation } from "react-i18next";

Expand Down Expand Up @@ -50,16 +57,24 @@ export default function UserMenu(props: UserMenuProps): ReactElement {
getIsAdmin().then(setIsAdmin);

return (
<React.Fragment>
<>
<Button
aria-controls="user-menu"
aria-haspopup="true"
onClick={handleClick}
color="secondary"
style={{ background: tabColor(props.currentTab, Path.UserSettings) }}
style={{
background: tabColor(props.currentTab, Path.UserSettings),
minWidth: 0,
padding: 0,
}}
id={`avatar-${idAffix}`}
>
<Hidden mdDown>{LocalStorage.getCurrentUser()?.username}</Hidden>
<Hidden mdDown>
<Typography style={{ margin: 5 }}>
{LocalStorage.getCurrentUser()?.username}
</Typography>
</Hidden>
{avatar ? (
<Avatar alt="User avatar" src={avatar} style={{ marginLeft: 5 }} />
) : (
Expand All @@ -76,7 +91,7 @@ export default function UserMenu(props: UserMenuProps): ReactElement {
>
<WrappedUserMenuList isAdmin={isAdmin} onSelect={handleClose} />
</Menu>
</React.Fragment>
</>
);
}

Expand Down
Loading

0 comments on commit 3178d80

Please sign in to comment.