378 lines
12 KiB
TypeScript
378 lines
12 KiB
TypeScript
import {
|
|
PropsWithChildren,
|
|
SyntheticEvent,
|
|
forwardRef,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
} from 'react'
|
|
import { Link } from 'react-router-dom'
|
|
import CssBaseline from '@mui/material/CssBaseline'
|
|
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar'
|
|
import { ThemeProvider, createTheme, styled } from '@mui/material/styles'
|
|
import Drawer from '@mui/material/Drawer'
|
|
import Toolbar from '@mui/material/Toolbar'
|
|
import Box from '@mui/material/Box'
|
|
import List from '@mui/material/List'
|
|
import Typography from '@mui/material/Typography'
|
|
import Divider from '@mui/material/Divider'
|
|
import StepIcon from '@mui/material/StepIcon'
|
|
import Tooltip from '@mui/material/Tooltip'
|
|
import Snackbar from '@mui/material/Snackbar'
|
|
import MuiAlert, { AlertProps, AlertColor } from '@mui/material/Alert'
|
|
import IconButton from '@mui/material/IconButton'
|
|
import Button from '@mui/material/Button'
|
|
import Dialog from '@mui/material/Dialog'
|
|
import DialogActions from '@mui/material/DialogActions'
|
|
import DialogContent from '@mui/material/DialogContent'
|
|
import DialogContentText from '@mui/material/DialogContentText'
|
|
import DialogTitle from '@mui/material/DialogTitle'
|
|
import MenuIcon from '@mui/icons-material/Menu'
|
|
import WarningIcon from '@mui/icons-material/Warning'
|
|
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
|
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
|
import ListItem from '@mui/material/ListItem'
|
|
import ListItemButton from '@mui/material/ListItemButton'
|
|
import ListItemIcon from '@mui/material/ListItemIcon'
|
|
import ListItemText from '@mui/material/ListItemText'
|
|
import Home from '@mui/icons-material/Home'
|
|
import Brightness4Icon from '@mui/icons-material/Brightness4'
|
|
import Brightness7Icon from '@mui/icons-material/Brightness7'
|
|
import LinkIcon from '@mui/icons-material/Link'
|
|
|
|
import { ShellContext } from 'contexts/ShellContext'
|
|
import { SettingsContext } from 'contexts/SettingsContext'
|
|
import { AlertOptions } from 'models/shell'
|
|
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
|
|
|
const drawerWidth = 240
|
|
|
|
const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
|
|
props,
|
|
ref
|
|
) {
|
|
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />
|
|
})
|
|
|
|
const Main = styled('main', { shouldForwardProp: prop => prop !== 'open' })<{
|
|
open?: boolean
|
|
}>(({ theme, open }) => ({
|
|
transition: theme.transitions.create('margin', {
|
|
easing: theme.transitions.easing.sharp,
|
|
duration: theme.transitions.duration.leavingScreen,
|
|
}),
|
|
marginLeft: `-${drawerWidth}px`,
|
|
...(open && {
|
|
transition: theme.transitions.create('margin', {
|
|
easing: theme.transitions.easing.easeOut,
|
|
duration: theme.transitions.duration.enteringScreen,
|
|
}),
|
|
marginLeft: 0,
|
|
}),
|
|
}))
|
|
|
|
interface AppBarProps extends MuiAppBarProps {
|
|
open?: boolean
|
|
}
|
|
|
|
const AppBar = styled(MuiAppBar, {
|
|
shouldForwardProp: prop => prop !== 'open',
|
|
})<AppBarProps>(({ theme, open }) => ({
|
|
transition: theme.transitions.create(['margin', 'width'], {
|
|
easing: theme.transitions.easing.sharp,
|
|
duration: theme.transitions.duration.leavingScreen,
|
|
}),
|
|
...(open && {
|
|
width: `calc(100% - ${drawerWidth}px)`,
|
|
marginLeft: `${drawerWidth}px`,
|
|
transition: theme.transitions.create(['margin', 'width'], {
|
|
easing: theme.transitions.easing.easeOut,
|
|
duration: theme.transitions.duration.enteringScreen,
|
|
}),
|
|
}),
|
|
}))
|
|
|
|
const DrawerHeader = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
padding: theme.spacing(0, 1),
|
|
// necessary for content to be below app bar
|
|
...theme.mixins.toolbar,
|
|
justifyContent: 'flex-end',
|
|
}))
|
|
|
|
export interface ShellProps extends PropsWithChildren {
|
|
userPeerId: string
|
|
appNeedsUpdate: boolean
|
|
}
|
|
|
|
export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|
const settingsContext = useContext(SettingsContext)
|
|
const [isAlertShowing, setIsAlertShowing] = useState(false)
|
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
|
const [doShowPeers, setDoShowPeers] = useState(false)
|
|
const [alertSeverity, setAlertSeverity] = useState<AlertColor>('info')
|
|
const [title, setTitle] = useState('')
|
|
const [alertText, setAlertText] = useState('')
|
|
const [numberOfPeers, setNumberOfPeers] = useState(1)
|
|
|
|
const showAlert = useCallback<
|
|
(message: string, options?: AlertOptions) => void
|
|
>((message, options) => {
|
|
setAlertText(message)
|
|
setAlertSeverity(options?.severity ?? 'info')
|
|
setIsAlertShowing(true)
|
|
}, [])
|
|
|
|
const shellContextValue = useMemo(
|
|
() => ({
|
|
numberOfPeers,
|
|
setDoShowPeers,
|
|
setNumberOfPeers,
|
|
setTitle,
|
|
showAlert,
|
|
}),
|
|
[numberOfPeers, setDoShowPeers, setNumberOfPeers, setTitle, showAlert]
|
|
)
|
|
|
|
const colorMode = settingsContext.getUserSettings().colorMode
|
|
|
|
const handleColorModeToggleClick = () => {
|
|
const newMode = colorMode === 'light' ? 'dark' : 'light'
|
|
settingsContext.updateUserSettings({ colorMode: newMode })
|
|
}
|
|
|
|
const theme = useMemo(
|
|
() =>
|
|
createTheme({
|
|
palette: {
|
|
mode: colorMode,
|
|
},
|
|
}),
|
|
[colorMode]
|
|
)
|
|
|
|
const handleAlertClose = (
|
|
_event?: SyntheticEvent | Event,
|
|
reason?: string
|
|
) => {
|
|
if (reason === 'clickaway') {
|
|
return
|
|
}
|
|
|
|
setIsAlertShowing(false)
|
|
}
|
|
|
|
useEffect(() => {
|
|
document.title = title
|
|
}, [title])
|
|
|
|
const handleDrawerOpen = () => {
|
|
setIsDrawerOpen(true)
|
|
}
|
|
|
|
const handleDrawerClose = () => {
|
|
setIsDrawerOpen(false)
|
|
}
|
|
|
|
const handleHomeLinkClick = () => {
|
|
setIsDrawerOpen(false)
|
|
}
|
|
|
|
const handleLinkButtonClick = async () => {
|
|
await navigator.clipboard.writeText(window.location.href)
|
|
|
|
shellContextValue.showAlert('Current URL copied to clipboard', {
|
|
severity: 'success',
|
|
})
|
|
}
|
|
|
|
const handleRestartClick = () => {
|
|
window.location.reload()
|
|
}
|
|
|
|
return (
|
|
<ShellContext.Provider value={shellContextValue}>
|
|
<ThemeProvider theme={theme}>
|
|
<CssBaseline />
|
|
<Dialog
|
|
open={appNeedsUpdate}
|
|
aria-labelledby="alert-dialog-title"
|
|
aria-describedby="alert-dialog-description"
|
|
>
|
|
<DialogTitle id="alert-dialog-title">
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
<WarningIcon
|
|
fontSize="medium"
|
|
sx={theme => ({
|
|
color: theme.palette.warning.main,
|
|
mr: theme.spacing(1),
|
|
})}
|
|
/>
|
|
Update needed
|
|
</Box>
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<DialogContentText id="alert-dialog-description">
|
|
In order to function properly, Chitchatter needs to be updated.
|
|
The update has already been installed in the background. All you
|
|
need to do is reload the page or click "Refresh" below.
|
|
</DialogContentText>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={handleRestartClick} autoFocus>
|
|
Refresh
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
<Box
|
|
className="Chitchatter"
|
|
sx={{
|
|
height: '100vh',
|
|
display: 'flex',
|
|
}}
|
|
>
|
|
<Snackbar
|
|
open={isAlertShowing}
|
|
autoHideDuration={6000}
|
|
onClose={handleAlertClose}
|
|
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
|
>
|
|
<Alert
|
|
onClose={handleAlertClose}
|
|
severity={alertSeverity}
|
|
variant="standard"
|
|
>
|
|
{alertText}
|
|
</Alert>
|
|
</Snackbar>
|
|
<AppBar position="fixed" open={isDrawerOpen}>
|
|
<Toolbar
|
|
variant="regular"
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
}}
|
|
>
|
|
<IconButton
|
|
size="large"
|
|
edge="start"
|
|
color="inherit"
|
|
aria-label="Open menu"
|
|
sx={{ mr: 2, ...(isDrawerOpen && { display: 'none' }) }}
|
|
onClick={handleDrawerOpen}
|
|
>
|
|
<MenuIcon />
|
|
</IconButton>
|
|
<Typography
|
|
variant="h6"
|
|
noWrap
|
|
component="div"
|
|
sx={{ marginRight: 'auto' }}
|
|
>
|
|
{title}
|
|
</Typography>
|
|
<Tooltip title="Copy current URL">
|
|
<IconButton
|
|
size="large"
|
|
edge="start"
|
|
color="inherit"
|
|
aria-label="Copy current URL"
|
|
sx={{ ml: 'auto' }}
|
|
onClick={handleLinkButtonClick}
|
|
>
|
|
<LinkIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
{doShowPeers ? (
|
|
<Tooltip title="Number of peers in the room">
|
|
<StepIcon icon={numberOfPeers} sx={{ ml: 2 }} />
|
|
</Tooltip>
|
|
) : null}
|
|
</Toolbar>
|
|
</AppBar>
|
|
<Drawer
|
|
sx={{
|
|
width: drawerWidth,
|
|
flexShrink: 0,
|
|
'& .MuiDrawer-paper': {
|
|
width: drawerWidth,
|
|
boxSizing: 'border-box',
|
|
},
|
|
}}
|
|
variant="persistent"
|
|
anchor="left"
|
|
open={isDrawerOpen}
|
|
>
|
|
<DrawerHeader>
|
|
<IconButton onClick={handleDrawerClose} aria-label="Close menu">
|
|
{theme.direction === 'ltr' ? (
|
|
<ChevronLeftIcon />
|
|
) : (
|
|
<ChevronRightIcon />
|
|
)}
|
|
</IconButton>
|
|
</DrawerHeader>
|
|
<Divider />
|
|
<ListItem disablePadding>
|
|
<ListItemText
|
|
sx={{
|
|
padding: '1em 1.5em',
|
|
}}
|
|
primary={
|
|
<Typography>
|
|
Your user name:{' '}
|
|
<PeerNameDisplay sx={{ fontWeight: 'bold' }}>
|
|
{userPeerId}
|
|
</PeerNameDisplay>
|
|
</Typography>
|
|
}
|
|
/>
|
|
</ListItem>
|
|
<Divider />
|
|
<List role="navigation">
|
|
<Link to="/" onClick={handleHomeLinkClick}>
|
|
<ListItem disablePadding>
|
|
<ListItemButton>
|
|
<ListItemIcon>
|
|
<Home />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Home" />
|
|
</ListItemButton>
|
|
</ListItem>
|
|
</Link>
|
|
<ListItem disablePadding>
|
|
<ListItemButton onClick={handleColorModeToggleClick}>
|
|
<ListItemIcon>
|
|
{theme.palette.mode === 'dark' ? (
|
|
<Brightness7Icon />
|
|
) : (
|
|
<Brightness4Icon />
|
|
)}
|
|
</ListItemIcon>
|
|
<ListItemText primary="Change theme" />
|
|
</ListItemButton>
|
|
</ListItem>
|
|
</List>
|
|
<Divider />
|
|
</Drawer>
|
|
<Main
|
|
open={isDrawerOpen}
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
width: '100%',
|
|
}}
|
|
>
|
|
<DrawerHeader />
|
|
<Box sx={{ overflow: 'auto', flexGrow: 1 }}>{children}</Box>
|
|
</Main>
|
|
</Box>
|
|
</ThemeProvider>
|
|
</ShellContext.Provider>
|
|
)
|
|
}
|