Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 134 additions & 161 deletions src/app/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

import * as React from 'react';

import { Link } from 'wouter';
import { Link, useLocation } from 'wouter';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import AppBar from '@mui/material/AppBar';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
Expand All @@ -19,19 +18,16 @@ import Paper from '@mui/material/Paper';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Avatar from '@mui/material/Avatar';
import { List } from 'immutable';
import { PopoverOrigin } from '@mui/material/Popover';
import AccountCircle from '@mui/icons-material/AccountCircle';
import MenuIcon from '@mui/icons-material/Menu';

import { NewProject } from './NewProject';
import { Project } from './Project';
import { User } from './User';

interface HomeState {
anchorEl?: HTMLElement;
projects: List<Project>;
}
import { Suspense, useEffect, useRef, useState } from 'react';
import { useMediaQuery } from '@mui/system';
import { styled } from '@mui/material';

interface HomeProps {
user: User;
Expand All @@ -44,162 +40,141 @@ const AnchorOrigin: PopoverOrigin = {
horizontal: 'right',
};

const Home = styled(
class HomeInner extends React.Component<HomeProps & { className?: string }, HomeState> {
state: HomeState;

constructor(props: HomeProps) {
super(props);

this.state = {
anchorEl: undefined,
projects: List<Project>(),
};

// eslint-disable-next-line @typescript-eslint/no-misused-promises
setTimeout(this.getProjects);
}

getProjects = async (): Promise<void> => {
const response = await fetch('/api/projects', { credentials: 'same-origin' });
const status = response.status;
if (!(status >= 200 && status < 400)) {
console.log("couldn't fetch projects.");
return;
}
const projects = (await response.json()) as Project[];
this.setState({
projects: List(projects),
});
};

handleClose = () => {
this.setState({
anchorEl: undefined,
});
};

handleMenu = (event: React.MouseEvent<HTMLElement>) => {
this.setState({
anchorEl: event.currentTarget,
});
};
async function fetchProjects() {
const response = await fetch('/api/projects', { credentials: 'same-origin' });
const status = response.status;
if (!(status >= 200 && status < 400)) {
console.log("Couldn't fetch projects.");
return [];
}
return (await response.json()) as Project[];
}

handleProjectCreated = (project: Project) => {
window.location.pathname = '/' + project.id;
};
function NewProjectForm({ user }: { user: User }) {
const [, navigate] = useLocation();
function handleProjectCreated(project: Project) {
navigate('/' + project.id);
}
return (
<div className="simlin-home-newprojectform">
<Grid container direction="row" justifyContent="center" alignItems="center">
<Grid item>
<NewProject user={user} onProjectCreated={handleProjectCreated} />
</Grid>
</Grid>
</div>
);
}

getGridListCols = () => {
// TODO: this should be 1 on small screens, but useMediaQuery doesn't
// work in class components, only function components.
return 2;
function Projects() {
const isLargeScreen = useMediaQuery('(min-width:600px)');
const gridListCols = isLargeScreen ? 2 : 1;
const [projects, setProjects] = useState<Project[]>([]);

useEffect(() => {
let ignore = false;
fetchProjects().then((projects) => {
if (!ignore) setProjects(projects);
});
return () => {
ignore = true;
};
}, []);

return (
<div className="simlin-home-projectgrid">
<ImageList cols={gridListCols} gap={0}>
{projects.map((project) => (
<ImageListItem key={project.id} style={{ height: 'auto' }}>
<Link to={`/${project.id}`} className="simlin-home-modellink">
<Paper className="simlin-home-paper" elevation={4}>
<div className="simlin-home-preview">
<img src={`/api/preview/${project.id}`} alt="model preview" className="simlin-home-previewimg" />
</div>
<Typography variant="h5" component="h3">
{project.displayName}
</Typography>
<Typography component="p">{project.description}&nbsp;</Typography>
</Paper>
</Link>
</ImageListItem>
))}
</ImageList>
</div>
);
}

newProjectForm() {
return (
<div className="simlin-home-newprojectform">
<Grid container direction="row" justifyContent="center" alignItems="center">
<Grid item>
<NewProject user={this.props.user} onProjectCreated={this.handleProjectCreated} />
</Grid>
</Grid>
</div>
);
}

projects() {
const { projects } = this.state;
return (
<div className="simlin-home-projectgrid">
<ImageList cols={this.getGridListCols()} gap={0}>
{projects.map((project) => (
<ImageListItem key={project.id} style={{ height: 'auto' }}>
<Link to={`/${project.id}`} className="simlin-home-modellink">
<Paper className="simlin-home-paper" elevation={4}>
<div className="simlin-home-preview">
<img src={`/api/preview/${project.id}`} alt="model preview" className="simlin-home-previewimg" />
</div>
<Typography variant="h5" component="h3">
{project.displayName}
</Typography>
<Typography component="p">{project.description}&nbsp;</Typography>
</Paper>
</Link>
</ImageListItem>
))}
</ImageList>
</div>
);
}

render() {
const { className } = this.props;
const { anchorEl } = this.state;
const { photoUrl } = this.props.user;
const open = Boolean(anchorEl);

const account = photoUrl ? (
<Avatar alt={this.props.user.displayName} src={photoUrl} className="simlin-home-avatar" />
function Home(props: HomeProps & { className?: string }) {
const anchorEl = useRef<HTMLButtonElement>(null);
const [isMenuOpen, setIsMenuOpen] = useState(false);

const account = props.user.photoUrl ? (
<Avatar alt={props.user.displayName} src={props.user.photoUrl} className="simlin-home-avatar" />
) : (
<AccountCircle />
);

return (
<div className={clsx(props.className, 'simlin-home-root')}>
<AppBar position="fixed">
<Toolbar variant="dense">
<IconButton className="simlin-home-menubutton" color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" className="simlin-home-flex">
<Link to="/" className="simlin-home-modellink">
Simlin
</Link>
{/*&nbsp;*/}
{/*<span className={classes.sdTitle}>*/}
{/* System Dynamics*/}
{/*</span>*/}
</Typography>
<div>
<Link to="/new" className="simlin-home-modellink">
<Button variant="outlined" className="simlin-home-newprojectbutton">
New Project
</Button>
</Link>

<IconButton
className="simlin-home-profileicon"
aria-owns={isMenuOpen ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={() => setIsMenuOpen(true)}
color="inherit"
ref={anchorEl}
>
{account}
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl.current}
anchorOrigin={AnchorOrigin}
transformOrigin={AnchorOrigin}
open={isMenuOpen}
onClose={() => setIsMenuOpen(false)}
>
<MenuItem onClick={() => setIsMenuOpen(false)}>Logout</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
<br />
<br />
<br />
{props.isNewProject ? (
<NewProjectForm user={props.user} />
) : (
<AccountCircle />
);

const content = this.props.isNewProject ? this.newProjectForm() : this.projects();

return (
<div className={clsx(className, 'simlin-home-root')}>
<AppBar position="fixed">
<Toolbar variant="dense">
<IconButton className="simlin-home-menubutton" color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" className="simlin-home-flex">
<Link to="/" className="simlin-home-modellink">
Simlin
</Link>
{/*&nbsp;*/}
{/*<span className={classes.sdTitle}>*/}
{/* System Dynamics*/}
{/*</span>*/}
</Typography>
<div>
<Link to="/new" className="simlin-home-modellink">
<Button variant="outlined" className="simlin-home-newprojectbutton">
New Project
</Button>
</Link>
<Suspense fallback={'Loading'}>
<Projects />
</Suspense>
)}
</div>
);
}

<IconButton
className="simlin-home-profileicon"
aria-owns={open ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit"
>
{account}
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={AnchorOrigin}
transformOrigin={AnchorOrigin}
open={open}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Logout</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
<br />
<br />
<br />
{content}
</div>
);
}
},
)(() => ({
export default styled(Home)({
'&.simlin-home-root': {
flexGrow: 1,
},
Expand Down Expand Up @@ -253,6 +228,4 @@ const Home = styled(
marginRight: 'auto',
maxWidth: 1024,
},
}));

export default Home;
});
Loading