214 lines
No EOL
7.5 KiB
TypeScript
214 lines
No EOL
7.5 KiB
TypeScript
import React, { useContext, useState } from 'react';
|
|
import {
|
|
Button, CssBaseline, Divider, IconButton, List, ListItem, Drawer,
|
|
AppBar, Toolbar, Typography, InputBase, ListItemIcon, ListItemText, Menu, MenuItem,
|
|
Hidden, Tooltip, Link, styled
|
|
} from '@mui/material';
|
|
import { alpha, Theme, useTheme } from '@mui/material/styles';
|
|
import {
|
|
ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, AccountCircle
|
|
} from '@mui/icons-material';
|
|
|
|
import { Link as RouterLink, useNavigate } from 'react-router-dom';
|
|
import { doLogout, UserContext } from '../state';
|
|
|
|
const drawerWidth = 270;
|
|
|
|
const DrawerHeader = styled('div')(({ theme }) => ({
|
|
...theme.mixins.toolbar
|
|
}));
|
|
|
|
const StyledDrawer = styled(Drawer)(({ theme }) => ({
|
|
flexShrink: 0,
|
|
whiteSpace: "nowrap",
|
|
[theme.breakpoints.up("sm")]: {
|
|
width: drawerWidth,
|
|
},
|
|
}
|
|
));
|
|
const StyledSearchBar = styled('div')(({ theme }) => ({
|
|
position: 'relative',
|
|
borderRadius: theme.shape.borderRadius,
|
|
backgroundColor: alpha(theme.palette.common.white, 0.15),
|
|
'&:hover': {
|
|
backgroundColor: alpha(theme.palette.common.white, 0.25),
|
|
},
|
|
marginLeft: 0,
|
|
width: '100%',
|
|
[theme.breakpoints.up('sm')]: {
|
|
marginLeft: theme.spacing(1),
|
|
width: 'auto',
|
|
},
|
|
}));
|
|
const StyledInputBase = styled(InputBase)(({ theme }) => ({
|
|
color: 'inherit',
|
|
'& .MuiInputBase-input': {
|
|
padding: theme.spacing(1, 1, 1, 0),
|
|
// vertical padding + font size from searchIcon
|
|
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
|
|
transition: theme.transitions.create('width'),
|
|
width: '100%',
|
|
[theme.breakpoints.up('sm')]: {
|
|
width: '12ch',
|
|
'&:focus': {
|
|
width: '20ch',
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
const closedMixin = (theme: Theme) => ({
|
|
overflowX: 'hidden',
|
|
width: `calc(${theme.spacing(7)} + 1px)`,
|
|
});
|
|
|
|
export const Headline = (prop: {
|
|
children?: React.ReactNode,
|
|
classes?: {
|
|
content?: string,
|
|
toolbar?: string,
|
|
},
|
|
menu: React.ReactNode
|
|
}) => {
|
|
const [v, setv] = useState(false);
|
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
|
const theme = useTheme();
|
|
const toggleV = () => setv(!v);
|
|
const handleProfileMenuOpen = (e: React.MouseEvent<HTMLElement>) => setAnchorEl(e.currentTarget);
|
|
const handleProfileMenuClose = () => setAnchorEl(null);
|
|
const isProfileMenuOpened = Boolean(anchorEl);
|
|
const menuId = 'primary-search-account-menu';
|
|
const user_ctx = useContext(UserContext);
|
|
const isLogin = user_ctx.username !== "";
|
|
const navigate = useNavigate();
|
|
const [search, setSearch] = useState("");
|
|
|
|
const renderProfileMenu = (<Menu
|
|
anchorEl={anchorEl}
|
|
anchorOrigin={{ horizontal: 'right', vertical: "top" }}
|
|
id={menuId}
|
|
open={isProfileMenuOpened}
|
|
keepMounted
|
|
transformOrigin={{ horizontal: 'right', vertical: "top" }}
|
|
onClose={handleProfileMenuClose}
|
|
>
|
|
<MenuItem component={RouterLink} to='/profile'>Profile</MenuItem>
|
|
<MenuItem onClick={async () => { handleProfileMenuClose(); await doLogout(); user_ctx.setUsername(""); }}>Logout</MenuItem>
|
|
</Menu>);
|
|
const drawer_contents = (<>
|
|
<DrawerHeader>
|
|
<IconButton onClick={toggleV}>
|
|
{theme.direction === "ltr" ? <ChevronLeft /> : <ChevronRight />}
|
|
</IconButton>
|
|
</DrawerHeader>
|
|
<Divider />
|
|
{prop.menu}
|
|
</>);
|
|
|
|
return (<div style={{ display: 'flex' }}>
|
|
<CssBaseline />
|
|
<AppBar position="fixed" sx={{
|
|
zIndex: theme.zIndex.drawer + 1,
|
|
transition: theme.transitions.create(['width', 'margin'], {
|
|
easing: theme.transitions.easing.sharp,
|
|
duration: theme.transitions.duration.leavingScreen
|
|
})
|
|
}}>
|
|
<Toolbar>
|
|
<IconButton color="inherit"
|
|
aria-label="open drawer"
|
|
onClick={toggleV}
|
|
edge="start"
|
|
style={{ marginRight: 36 }}
|
|
>
|
|
<MenuIcon></MenuIcon>
|
|
</IconButton>
|
|
<Link variant="h5" noWrap sx={{
|
|
display: 'none',
|
|
[theme.breakpoints.up("sm")]: {
|
|
display: 'block'
|
|
}
|
|
}} color="inherit" component={RouterLink} to="/">
|
|
Ionian
|
|
</Link>
|
|
<div style={{ flexGrow: 1 }}></div>
|
|
<StyledSearchBar >
|
|
<div style={{
|
|
padding: theme.spacing(0, 2),
|
|
height: '100%',
|
|
position: 'absolute',
|
|
pointerEvents: 'none',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center'
|
|
}}>
|
|
<SearchIcon onClick={() => navSearch(search)} />
|
|
</div>
|
|
<StyledInputBase placeholder="search"
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
onKeyUp={(e) => {
|
|
if (e.key === "Enter") {
|
|
navSearch(search);
|
|
}
|
|
}}
|
|
value={search}></StyledInputBase>
|
|
</StyledSearchBar>
|
|
{
|
|
isLogin ?
|
|
<IconButton
|
|
edge="end"
|
|
aria-label="account of current user"
|
|
aria-controls={menuId}
|
|
aria-haspopup="true"
|
|
onClick={handleProfileMenuOpen}
|
|
color="inherit">
|
|
<AccountCircle />
|
|
</IconButton>
|
|
: <Button color="inherit" component={RouterLink} to="/login">Login</Button>
|
|
}
|
|
</Toolbar>
|
|
</AppBar>
|
|
{renderProfileMenu}
|
|
<nav style={{ width: theme.spacing(7) }}>
|
|
<Hidden smUp implementation="css">
|
|
<StyledDrawer variant="temporary" anchor='left' open={v} onClose={toggleV}
|
|
sx={{
|
|
width: drawerWidth
|
|
}}
|
|
>
|
|
{drawer_contents}
|
|
</StyledDrawer>
|
|
</Hidden>
|
|
<Hidden xsDown implementation="css">
|
|
<StyledDrawer variant='permanent' anchor='left'
|
|
sx={{
|
|
...closedMixin(theme),
|
|
'& .MuiDrawer-paper': closedMixin(theme),
|
|
}}>
|
|
{drawer_contents}
|
|
</StyledDrawer>
|
|
</Hidden>
|
|
</nav>
|
|
<main style={{
|
|
display: 'flex',
|
|
flexFlow: 'column',
|
|
flexGrow: 1,
|
|
padding: theme.spacing(3),
|
|
marginTop: theme.spacing(6),
|
|
}}>
|
|
<div style={{
|
|
}} ></div>
|
|
{prop.children}
|
|
</main>
|
|
</div>);
|
|
function navSearch(search: string){
|
|
let words = search.includes("&") ? search.split("&") : [search];
|
|
words = words.map(w => w.trim())
|
|
.map(w => w.includes(":") ?
|
|
`allow_tag=${w}`
|
|
: `word=${encodeURIComponent(w)}`);
|
|
navigate(`/search?${words.join("&")}`);
|
|
}
|
|
};
|
|
|
|
export default Headline; |