ionian/src/client/component/headline.tsx
2022-06-29 21:48:01 +09:00

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;