mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
update navigation for consistency
This commit is contained in:
@@ -107,7 +107,8 @@ export default function App() {
|
||||
path={routes.home}
|
||||
render={() => (
|
||||
<Navigation
|
||||
title={
|
||||
title="Home"
|
||||
titleComponent={
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Logo />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useLocation, Link } from "react-router-dom";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
import {
|
||||
Drawer,
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
Stack,
|
||||
IconButton,
|
||||
List,
|
||||
MenuItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Divider,
|
||||
@@ -19,7 +18,8 @@ import CloseIcon from "assets/icons/Backburger";
|
||||
|
||||
import { APP_BAR_HEIGHT } from ".";
|
||||
import Logo from "assets/Logo";
|
||||
import NavDrawerItem from "./NavDrawerItem";
|
||||
import NavItem from "./NavItem";
|
||||
import NavTableSection from "./NavTableSection";
|
||||
|
||||
import { useRowyContext } from "contexts/RowyContext";
|
||||
import { routes } from "constants/routes";
|
||||
@@ -28,17 +28,14 @@ export const NAV_DRAWER_WIDTH = 256;
|
||||
|
||||
export interface INavDrawerProps extends DrawerProps {
|
||||
currentSection?: string;
|
||||
currentTable?: string;
|
||||
onClose: NonNullable<DrawerProps["onClose"]>;
|
||||
}
|
||||
|
||||
export default function NavDrawer({
|
||||
currentSection,
|
||||
currentTable = "",
|
||||
...props
|
||||
}: INavDrawerProps) {
|
||||
const { userClaims, sections } = useRowyContext();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const closeDrawer = (e: {}) => props.onClose(e, "escapeKeyDown");
|
||||
|
||||
@@ -67,72 +64,51 @@ export default function NavDrawer({
|
||||
<nav>
|
||||
<List disablePadding>
|
||||
<li>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
to={routes.home}
|
||||
selected={pathname === routes.home}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<NavItem to={routes.home} onClick={closeDrawer}>
|
||||
<ListItemIcon>
|
||||
<HomeIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Home" />
|
||||
</MenuItem>
|
||||
</NavItem>
|
||||
</li>
|
||||
<li>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
to={routes.userSettings}
|
||||
selected={pathname === routes.userSettings}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<NavItem to={routes.userSettings} onClick={closeDrawer}>
|
||||
<ListItemIcon>
|
||||
<SettingsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Settings" />
|
||||
</MenuItem>
|
||||
</NavItem>
|
||||
</li>
|
||||
{userClaims?.roles?.includes("ADMIN") && (
|
||||
<li>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
to={routes.projectSettings}
|
||||
selected={pathname === routes.projectSettings}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<NavItem to={routes.projectSettings} onClick={closeDrawer}>
|
||||
<ListItemIcon>
|
||||
<ProjectSettingsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Project Settings" />
|
||||
</MenuItem>
|
||||
</NavItem>
|
||||
</li>
|
||||
)}
|
||||
{userClaims?.roles?.includes("ADMIN") && (
|
||||
<li>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
to={routes.userManagement}
|
||||
selected={pathname === routes.userManagement}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<NavItem to={routes.userManagement} onClick={closeDrawer}>
|
||||
<ListItemIcon>
|
||||
<UserManagementIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="User Management" />
|
||||
</MenuItem>
|
||||
</NavItem>
|
||||
</li>
|
||||
)}
|
||||
|
||||
<Divider variant="middle" sx={{ mt: 1, mb: 1 }} />
|
||||
<Divider variant="middle" sx={{ my: 1 }} />
|
||||
|
||||
{sections &&
|
||||
Object.entries(sections).map(([section, tables]) => (
|
||||
<NavDrawerItem
|
||||
<NavTableSection
|
||||
key={section}
|
||||
section={section}
|
||||
tables={tables}
|
||||
currentSection={currentSection}
|
||||
currentTable={currentTable}
|
||||
closeDrawer={closeDrawer}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { makeStyles, createStyles } from "@material-ui/styles";
|
||||
import { List, MenuItem, ListItemText, Collapse } from "@material-ui/core";
|
||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
|
||||
import { Table } from "contexts/RowyContext";
|
||||
import { routes } from "constants/routes";
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
listItem: {
|
||||
width: `calc(100% - ${theme.spacing(2)})`,
|
||||
margin: theme.spacing(0, 1),
|
||||
padding: theme.spacing(0.75, 0.25, 0.75, 1),
|
||||
},
|
||||
listItemSelected: {
|
||||
"&&, &&:hover": { backgroundColor: theme.palette.action.selected },
|
||||
"$listItem&:before": {
|
||||
top: (36 - 16) / 2,
|
||||
},
|
||||
},
|
||||
listItemIcon: {},
|
||||
listItemText: {
|
||||
...theme.typography.body2,
|
||||
display: "block",
|
||||
color: "inherit",
|
||||
},
|
||||
|
||||
dropdownIcon: {
|
||||
color: theme.palette.action.active,
|
||||
transition: theme.transitions.create("transform"),
|
||||
},
|
||||
dropdownIconOpen: { transform: "rotate(180deg)" },
|
||||
|
||||
childListItem: {
|
||||
minHeight: 36,
|
||||
paddingLeft: theme.spacing(4),
|
||||
},
|
||||
childListItemText: {
|
||||
...theme.typography.body2,
|
||||
display: "block",
|
||||
color: "inherit",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
export interface INavDrawerItemProps {
|
||||
section: string;
|
||||
tables: Table[];
|
||||
|
||||
currentSection?: string;
|
||||
currentTable: string;
|
||||
|
||||
closeDrawer: (e: {}) => void;
|
||||
}
|
||||
|
||||
export default function NavDrawerItem({
|
||||
section,
|
||||
tables,
|
||||
currentSection,
|
||||
currentTable,
|
||||
closeDrawer,
|
||||
}: INavDrawerItemProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const [open, setOpen] = useState(section === currentSection);
|
||||
|
||||
return (
|
||||
<li>
|
||||
<MenuItem
|
||||
// button
|
||||
classes={{
|
||||
root: clsx(
|
||||
classes.listItem,
|
||||
!open && currentSection === section && classes.listItemSelected
|
||||
),
|
||||
}}
|
||||
selected={!open && currentSection === section}
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
>
|
||||
<ListItemText
|
||||
primary={section}
|
||||
classes={{ primary: classes.listItemText }}
|
||||
/>
|
||||
|
||||
<ArrowDropDownIcon
|
||||
className={clsx(
|
||||
classes.dropdownIcon,
|
||||
open && classes.dropdownIconOpen
|
||||
)}
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
<Collapse in={open}>
|
||||
<List disablePadding>
|
||||
{tables.map((table) => (
|
||||
<li key={table.collection}>
|
||||
<MenuItem
|
||||
// button
|
||||
selected={table.collection === currentTable}
|
||||
classes={{
|
||||
root: clsx(classes.listItem, classes.childListItem),
|
||||
selected: classes.listItemSelected,
|
||||
}}
|
||||
component={Link}
|
||||
to={
|
||||
table.isCollectionGroup
|
||||
? `${routes.tableGroup}/${table.collection}`
|
||||
: `${routes.table}/${table.collection.replace(
|
||||
/\//g,
|
||||
"~2F"
|
||||
)}`
|
||||
}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<ListItemText
|
||||
primary={table.name}
|
||||
classes={{ primary: classes.childListItemText }}
|
||||
/>
|
||||
</MenuItem>
|
||||
</li>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
20
src/components/Navigation/NavItem.tsx
Normal file
20
src/components/Navigation/NavItem.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { MenuItem, MenuItemProps } from "@material-ui/core";
|
||||
|
||||
export default function NavItem(props: MenuItemProps<Link>) {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
component={Link}
|
||||
selected={pathname === props.to}
|
||||
{...props}
|
||||
sx={{
|
||||
"&&::before": {
|
||||
left: "auto",
|
||||
right: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
66
src/components/Navigation/NavTableSection.tsx
Normal file
66
src/components/Navigation/NavTableSection.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { List, ListItemText, Collapse } from "@material-ui/core";
|
||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
import NavItem from "./NavItem";
|
||||
|
||||
import { Table } from "contexts/RowyContext";
|
||||
import { routes } from "constants/routes";
|
||||
|
||||
export interface INavDrawerItemProps {
|
||||
section: string;
|
||||
tables: Table[];
|
||||
currentSection?: string;
|
||||
closeDrawer: (e: {}) => void;
|
||||
}
|
||||
|
||||
export default function NavDrawerItem({
|
||||
section,
|
||||
tables,
|
||||
currentSection,
|
||||
closeDrawer,
|
||||
}: INavDrawerItemProps) {
|
||||
const [open, setOpen] = useState(section === currentSection);
|
||||
|
||||
return (
|
||||
<li>
|
||||
<NavItem
|
||||
{...({ component: "button" } as any)}
|
||||
selected={!open && currentSection === section}
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
>
|
||||
<ListItemText primary={section} style={{ textAlign: "left" }} />
|
||||
|
||||
<ArrowDropDownIcon
|
||||
sx={{
|
||||
color: "action.active",
|
||||
transform: open ? "rotate(180deg)" : "rotate(0)",
|
||||
transition: (theme) => theme.transitions.create("transform"),
|
||||
}}
|
||||
/>
|
||||
</NavItem>
|
||||
|
||||
<Collapse in={open}>
|
||||
<List disablePadding>
|
||||
{tables.map((table) => (
|
||||
<li key={table.collection}>
|
||||
<NavItem
|
||||
to={
|
||||
table.isCollectionGroup
|
||||
? `${routes.tableGroup}/${table.collection}`
|
||||
: `${routes.table}/${table.collection.replace(
|
||||
/\//g,
|
||||
"~2F"
|
||||
)}`
|
||||
}
|
||||
onClick={closeDrawer}
|
||||
>
|
||||
<ListItemText primary={table.name} sx={{ pl: 2 }} />
|
||||
</NavItem>
|
||||
</li>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
IconButton,
|
||||
Box,
|
||||
Typography,
|
||||
Fade,
|
||||
} from "@material-ui/core";
|
||||
import MenuIcon from "@material-ui/icons/Menu";
|
||||
|
||||
@@ -21,24 +20,22 @@ export const APP_BAR_HEIGHT = 56;
|
||||
|
||||
export interface INavigationProps {
|
||||
children: ReactNode;
|
||||
title?: ReactNode;
|
||||
title: string;
|
||||
titleComponent?: ReactNode;
|
||||
currentSection?: string;
|
||||
currentTable?: string;
|
||||
}
|
||||
|
||||
export default function Navigation({
|
||||
children,
|
||||
title,
|
||||
titleComponent,
|
||||
currentSection,
|
||||
currentTable,
|
||||
}: INavigationProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (typeof title === "string")
|
||||
document.title = `${title} | ${projectId} | ${name}`;
|
||||
|
||||
const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0 });
|
||||
|
||||
document.title = `${title} • ${projectId} • ${name}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBar
|
||||
@@ -88,12 +85,10 @@ export default function Navigation({
|
||||
</IconButton>
|
||||
|
||||
<Box sx={{ flex: 1, userSelect: "none" }}>
|
||||
{typeof title === "string" ? (
|
||||
{titleComponent || (
|
||||
<Typography variant="h6" component="h1" textAlign="center">
|
||||
{title}
|
||||
</Typography>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -106,7 +101,6 @@ export default function Navigation({
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
currentSection={currentSection}
|
||||
currentTable={currentTable}
|
||||
/>
|
||||
|
||||
{children}
|
||||
|
||||
@@ -4,17 +4,17 @@ import OpenInNewIcon from "@material-ui/icons/OpenInNew";
|
||||
|
||||
import { IProjectSettingsChildProps } from "pages/Settings/ProjectSettings";
|
||||
import WIKI_LINKS from "constants/wikiLinks";
|
||||
import { repository } from "@root/package.json";
|
||||
import { name, repository } from "@root/package.json";
|
||||
|
||||
export default function FunctionsBuilder({
|
||||
export default function CloudRun({
|
||||
settings,
|
||||
updateSettings,
|
||||
}: IProjectSettingsChildProps) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
Functions Builder is a Cloud Run instance that deploys this project’s
|
||||
Cloud Functions.{" "}
|
||||
{name} Run is a Cloud Run instance that deploys this project’s Cloud
|
||||
Functions.{" "}
|
||||
<Link
|
||||
href={WIKI_LINKS.functions}
|
||||
target="_blank"
|
||||
@@ -38,8 +38,8 @@ export default function FunctionsBuilder({
|
||||
>
|
||||
<Grid item xs={12} sm>
|
||||
<Typography>
|
||||
If you have not yet deployed Functions Builder, click this button
|
||||
and follow the prompts on Cloud Shell.
|
||||
If you have not yet deployed {name} Run, click this button and
|
||||
follow the prompts on Cloud Shell.
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
@@ -53,11 +53,13 @@ export default function FunctionsBuilder({
|
||||
rel="noopener noreferrer"
|
||||
endIcon={<OpenInNewIcon aria-label="Open in new tab" />}
|
||||
loading={
|
||||
settings.buildStatus === "BUILDING" ||
|
||||
settings.buildStatus === "COMPLETE"
|
||||
settings.cloudRunDeployStatus === "BUILDING" ||
|
||||
settings.cloudRunDeployStatus === "COMPLETE"
|
||||
}
|
||||
loadingIndicator={
|
||||
settings.buildStatus === "COMPLETE" ? "Deployed" : undefined
|
||||
settings.cloudRunDeployStatus === "COMPLETE"
|
||||
? "Deployed"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Deploy to Cloud Run
|
||||
@@ -68,9 +70,9 @@ export default function FunctionsBuilder({
|
||||
|
||||
<TextField
|
||||
label="Cloud Run Instance URL"
|
||||
id="buildUrl"
|
||||
defaultValue={settings.buildUrl}
|
||||
onChange={(e) => updateSettings({ buildUrl: e.target.value })}
|
||||
id="cloudRunUrl"
|
||||
defaultValue={settings.cloudRunUrl}
|
||||
onChange={(e) => updateSettings({ cloudRunUrl: e.target.value })}
|
||||
fullWidth
|
||||
placeholder="https://<id>.run.app"
|
||||
type="url"
|
||||
@@ -71,14 +71,8 @@ const RowyContext = React.createContext<Partial<RowyContextProps>>({});
|
||||
export default RowyContext;
|
||||
|
||||
export const rowyUser = (currentUser) => {
|
||||
const {
|
||||
displayName,
|
||||
email,
|
||||
uid,
|
||||
emailVerified,
|
||||
isAnonymous,
|
||||
photoURL,
|
||||
} = currentUser;
|
||||
const { displayName, email, uid, emailVerified, isAnonymous, photoURL } =
|
||||
currentUser;
|
||||
return {
|
||||
timestamp: new Date(),
|
||||
displayName,
|
||||
@@ -112,7 +106,7 @@ export const RowyContextProvider: React.FC = ({ children }) => {
|
||||
)
|
||||
.map((table) => ({
|
||||
...table,
|
||||
section: table.section ? table.section.trim() : "OTHER",
|
||||
section: table.section ? table.section.trim() : "Other",
|
||||
}));
|
||||
|
||||
const _sections = _groupBy(filteredTables, "section");
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Container, Stack } from "@material-ui/core";
|
||||
import SettingsSkeleton from "components/Settings/SettingsSkeleton";
|
||||
import SettingsSection from "components/Settings/SettingsSection";
|
||||
import About from "components/Settings/ProjectSettings/About";
|
||||
import CloudRun from "@src/components/Settings/ProjectSettings/CloudRun";
|
||||
import Authentication from "components/Settings/ProjectSettings/Authentication";
|
||||
import FunctionsBuilder from "components/Settings/ProjectSettings/FunctionsBuilder";
|
||||
|
||||
import { SETTINGS, PUBLIC_SETTINGS } from "config/dbPaths";
|
||||
import useDoc from "hooks/useDoc";
|
||||
@@ -12,6 +12,7 @@ import { db } from "@src/firebase";
|
||||
import { useSnackContext } from "contexts/SnackContext";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { useEffect } from "react";
|
||||
import { name } from "@root/package.json";
|
||||
|
||||
export interface IProjectSettingsChildProps {
|
||||
settings: Record<string, any>;
|
||||
@@ -81,12 +82,12 @@ export default function ProjectSettings() {
|
||||
<About />
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title="Authentication">
|
||||
<Authentication {...childProps} />
|
||||
<SettingsSection title={`${name} Run`}>
|
||||
<CloudRun {...childProps} />
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title="Functions Builder">
|
||||
<FunctionsBuilder {...childProps} />
|
||||
<SettingsSection title="Authentication">
|
||||
<Authentication {...childProps} />
|
||||
</SettingsSection>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -20,9 +20,6 @@ import useRouter from "hooks/useRouter";
|
||||
import { DocActions } from "hooks/useDoc";
|
||||
import ActionParamsProvider from "components/fields/Action/FormDialog/Provider";
|
||||
|
||||
import { name } from "@root/package.json";
|
||||
import { projectId } from "@src/firebase";
|
||||
|
||||
export default function TablePage() {
|
||||
const router = useRouter();
|
||||
const tableCollection = decodeURIComponent(router.match.params.id);
|
||||
@@ -36,16 +33,8 @@ export default function TablePage() {
|
||||
tableCollection?.split("/")[0],
|
||||
])?.section;
|
||||
const currentTable = tableCollection?.split("/")[0];
|
||||
|
||||
useEffect(() => {
|
||||
const tableName =
|
||||
_find(tables, ["collection", currentTable])?.name || currentTable;
|
||||
document.title = `${tableName} | ${projectId} | ${name}`;
|
||||
|
||||
return () => {
|
||||
document.title = `${projectId} | ${name}`;
|
||||
};
|
||||
}, [currentTable]);
|
||||
const tableName =
|
||||
_find(tables, ["collection", currentTable])?.name || currentTable;
|
||||
|
||||
let filters: RowyFilter[] = [];
|
||||
const parsed = queryString.parse(router.location.search);
|
||||
@@ -77,9 +66,9 @@ export default function TablePage() {
|
||||
|
||||
return (
|
||||
<Navigation
|
||||
title={<Breadcrumbs />}
|
||||
title={tableName}
|
||||
titleComponent={<Breadcrumbs />}
|
||||
currentSection={currentSection}
|
||||
currentTable={currentTable}
|
||||
>
|
||||
<ActionParamsProvider>
|
||||
{tableState.loadingColumns && (
|
||||
|
||||
Reference in New Issue
Block a user