forked from Shiloh/remnantchat
* feat: [#14] Display peer list Co-authored-by: Jeremy Kahn <jeremyckahn@gmail.com>
This commit is contained in:
parent
c38a203f07
commit
26618c0309
@ -9,9 +9,10 @@ import { ShellContext } from 'contexts/ShellContext'
|
||||
import { SettingsContext } from 'contexts/SettingsContext'
|
||||
import { usePeerRoom, usePeerRoomAction } from 'hooks/usePeerRoom'
|
||||
import { PeerActions } from 'models/network'
|
||||
import { UnsentMessage, ReceivedMessage } from 'models/chat'
|
||||
import { Peer, ReceivedMessage, UnsentMessage } from 'models/chat'
|
||||
import { MessageForm } from 'components/MessageForm'
|
||||
import { ChatTranscript } from 'components/ChatTranscript'
|
||||
import { funAnimalName } from 'fun-animal-names'
|
||||
import { getPeerName } from 'components/PeerNameDisplay'
|
||||
import { NotificationService } from 'services/Notification'
|
||||
import { Audio } from 'services/Audio'
|
||||
@ -49,10 +50,15 @@ export function Room({
|
||||
roomId
|
||||
)
|
||||
|
||||
const [sendPeerId, receivePeerId] = usePeerRoomAction<string>(
|
||||
peerRoom,
|
||||
PeerActions.PEER_NAME
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
shellContext.setDoShowPeers(true)
|
||||
|
||||
peerRoom.onPeerJoin(() => {
|
||||
peerRoom.onPeerJoin((peerId: string) => {
|
||||
shellContext.showAlert(`Someone has joined the room`, {
|
||||
severity: 'success',
|
||||
})
|
||||
@ -60,22 +66,53 @@ export function Room({
|
||||
const newNumberOfPeers = numberOfPeers + 1
|
||||
setNumberOfPeers(newNumberOfPeers)
|
||||
shellContext.setNumberOfPeers(newNumberOfPeers)
|
||||
;(async () => {
|
||||
try {
|
||||
await sendPeerId(userId, peerId)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})()
|
||||
})
|
||||
|
||||
peerRoom.onPeerLeave(() => {
|
||||
shellContext.showAlert(`Someone has left the room`, {
|
||||
severity: 'warning',
|
||||
})
|
||||
peerRoom.onPeerLeave((peerId: string) => {
|
||||
const peerIndex = shellContext.peerList.findIndex(
|
||||
peer => peer.peerId === peerId
|
||||
)
|
||||
const peerExist = peerIndex !== -1
|
||||
shellContext.showAlert(
|
||||
`${
|
||||
peerExist
|
||||
? funAnimalName(shellContext.peerList[peerIndex].userId)
|
||||
: 'Someone'
|
||||
} has left the room`,
|
||||
{
|
||||
severity: 'warning',
|
||||
}
|
||||
)
|
||||
|
||||
const newNumberOfPeers = numberOfPeers - 1
|
||||
setNumberOfPeers(newNumberOfPeers)
|
||||
shellContext.setNumberOfPeers(newNumberOfPeers)
|
||||
|
||||
if (peerExist) {
|
||||
const peerListClone = [...shellContext.peerList]
|
||||
peerListClone.splice(peerIndex, 1)
|
||||
shellContext.setPeerList(peerListClone)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
shellContext.setDoShowPeers(false)
|
||||
}
|
||||
}, [numberOfPeers, peerRoom, shellContext])
|
||||
}, [
|
||||
numberOfPeers,
|
||||
shellContext.peerList,
|
||||
peerRoom,
|
||||
sendPeerId,
|
||||
shellContext,
|
||||
userId,
|
||||
])
|
||||
|
||||
const [sendMessage, receiveMessage] = usePeerRoomAction<UnsentMessage>(
|
||||
peerRoom,
|
||||
@ -103,6 +140,26 @@ export function Room({
|
||||
setIsMessageSending(false)
|
||||
}
|
||||
|
||||
const upsertToPeerList = (peerToAdd: Peer) => {
|
||||
const peerIndex = shellContext.peerList.findIndex(
|
||||
peer => peer.peerId === peerToAdd.peerId
|
||||
)
|
||||
if (peerIndex === -1) {
|
||||
shellContext.setPeerList([
|
||||
...shellContext.peerList,
|
||||
{ peerId: peerToAdd.peerId, userId: peerToAdd.userId },
|
||||
])
|
||||
} else {
|
||||
const peerListClone = [...shellContext.peerList]
|
||||
peerListClone[peerIndex].userId = peerToAdd.userId
|
||||
shellContext.setPeerList(peerListClone)
|
||||
}
|
||||
}
|
||||
|
||||
receivePeerId((userId: string, peerId?: string) => {
|
||||
if (peerId) upsertToPeerList({ peerId, userId })
|
||||
})
|
||||
|
||||
receiveMessage(message => {
|
||||
const userSettings = settingsContext.getUserSettings()
|
||||
|
||||
|
65
src/components/Shell/PeerList.tsx
Normal file
65
src/components/Shell/PeerList.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
import MuiDrawer from '@mui/material/Drawer'
|
||||
import List from '@mui/material/List'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
||||
import ListItemButton from '@mui/material/ListItemButton'
|
||||
import Typography from '@mui/material/Typography'
|
||||
|
||||
import { PeerListHeader } from 'components/Shell/PeerListHeader'
|
||||
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
||||
|
||||
import { Peer } from 'models/chat'
|
||||
|
||||
export const peerListWidth = 240
|
||||
|
||||
export interface PeerListProps extends PropsWithChildren {
|
||||
userId: string
|
||||
isPeerListOpen: boolean
|
||||
onPeerListClose: () => void
|
||||
peerList: Peer[]
|
||||
}
|
||||
|
||||
export const PeerList = ({
|
||||
userId,
|
||||
isPeerListOpen,
|
||||
onPeerListClose,
|
||||
peerList,
|
||||
}: PeerListProps) => {
|
||||
return (
|
||||
<MuiDrawer
|
||||
sx={{
|
||||
width: peerListWidth,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: peerListWidth,
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
}}
|
||||
variant="persistent"
|
||||
anchor="right"
|
||||
open={isPeerListOpen}
|
||||
>
|
||||
<PeerListHeader>
|
||||
<IconButton onClick={onPeerListClose} aria-label="Close peer list">
|
||||
<ChevronRightIcon />
|
||||
</IconButton>
|
||||
</PeerListHeader>
|
||||
<Divider />
|
||||
<List>
|
||||
<ListItemButton>
|
||||
<Typography>
|
||||
<PeerNameDisplay>{userId}</PeerNameDisplay> (you)
|
||||
</Typography>
|
||||
</ListItemButton>
|
||||
{peerList.map((peer: Peer) => (
|
||||
<ListItemButton key={peer.peerId}>
|
||||
<PeerNameDisplay>{peer.userId}</PeerNameDisplay>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
<Divider />
|
||||
</MuiDrawer>
|
||||
)
|
||||
}
|
10
src/components/Shell/PeerListHeader.tsx
Normal file
10
src/components/Shell/PeerListHeader.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { styled } from '@mui/material/styles'
|
||||
|
||||
export const PeerListHeader = 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-start',
|
||||
}))
|
@ -4,32 +4,51 @@ import { styled } from '@mui/material/styles'
|
||||
|
||||
import { DrawerHeader } from './DrawerHeader'
|
||||
import { drawerWidth } from './Drawer'
|
||||
import { peerListWidth } from './PeerList'
|
||||
|
||||
const Main = styled('main', { shouldForwardProp: prop => prop !== 'open' })<{
|
||||
open?: boolean
|
||||
}>(({ theme, open }) => ({
|
||||
const Main = styled('main', {
|
||||
shouldForwardProp: prop =>
|
||||
prop !== 'isDrawerOpen' && prop !== 'isPeerListOpen',
|
||||
})<{
|
||||
isDrawerOpen?: boolean
|
||||
isPeerListOpen?: boolean
|
||||
}>(({ theme, isDrawerOpen, isPeerListOpen }) => ({
|
||||
transition: theme.transitions.create('margin', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
marginLeft: `-${drawerWidth}px`,
|
||||
...(open && {
|
||||
marginRight: `-${peerListWidth}px`,
|
||||
...(isDrawerOpen && {
|
||||
transition: theme.transitions.create('margin', {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
marginLeft: 0,
|
||||
}),
|
||||
...(isPeerListOpen && {
|
||||
transition: theme.transitions.create('margin', {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
marginRight: 0,
|
||||
}),
|
||||
}))
|
||||
|
||||
interface RouteContentProps extends PropsWithChildren {
|
||||
isDrawerOpen: boolean
|
||||
isPeerListOpen: boolean
|
||||
}
|
||||
|
||||
export const RouteContent = ({ children, isDrawerOpen }: RouteContentProps) => {
|
||||
export const RouteContent = ({
|
||||
children,
|
||||
isDrawerOpen,
|
||||
isPeerListOpen,
|
||||
}: RouteContentProps) => {
|
||||
return (
|
||||
<Main
|
||||
open={isDrawerOpen}
|
||||
isDrawerOpen={isDrawerOpen}
|
||||
isPeerListOpen={isPeerListOpen}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
@ -15,6 +15,7 @@ import { AlertColor } from '@mui/material/Alert'
|
||||
import { ShellContext } from 'contexts/ShellContext'
|
||||
import { SettingsContext } from 'contexts/SettingsContext'
|
||||
import { AlertOptions } from 'models/shell'
|
||||
import { Peer } from 'models/chat'
|
||||
import { ErrorBoundary } from 'components/ErrorBoundary'
|
||||
|
||||
import { Drawer } from './Drawer'
|
||||
@ -22,6 +23,7 @@ import { UpgradeDialog } from './UpgradeDialog'
|
||||
import { ShellAppBar } from './ShellAppBar'
|
||||
import { NotificationArea } from './NotificationArea'
|
||||
import { RouteContent } from './RouteContent'
|
||||
import { PeerList } from './PeerList'
|
||||
|
||||
export interface ShellProps extends PropsWithChildren {
|
||||
userPeerId: string
|
||||
@ -37,6 +39,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
const [title, setTitle] = useState('')
|
||||
const [alertText, setAlertText] = useState('')
|
||||
const [numberOfPeers, setNumberOfPeers] = useState(1)
|
||||
const [isPeerListOpen, setIsPeerListOpen] = useState(false)
|
||||
const [peerList, setPeerList] = useState<Peer[]>([]) // except you
|
||||
const [tabHasFocus, setTabHasFocus] = useState(true)
|
||||
|
||||
const showAlert = useCallback<
|
||||
@ -55,9 +59,15 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
setNumberOfPeers,
|
||||
setTitle,
|
||||
showAlert,
|
||||
isPeerListOpen,
|
||||
setIsPeerListOpen,
|
||||
peerList,
|
||||
setPeerList,
|
||||
}),
|
||||
[
|
||||
isPeerListOpen,
|
||||
numberOfPeers,
|
||||
peerList,
|
||||
tabHasFocus,
|
||||
setDoShowPeers,
|
||||
setNumberOfPeers,
|
||||
@ -112,6 +122,10 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
setIsDrawerOpen(true)
|
||||
}
|
||||
|
||||
const handlePeerListOpen = () => {
|
||||
setIsPeerListOpen(true)
|
||||
}
|
||||
|
||||
const handleLinkButtonClick = async () => {
|
||||
await navigator.clipboard.writeText(window.location.href)
|
||||
|
||||
@ -124,6 +138,10 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
setIsDrawerOpen(false)
|
||||
}
|
||||
|
||||
const handlePeerListClose = () => {
|
||||
setIsPeerListOpen(false)
|
||||
}
|
||||
|
||||
const handleHomeLinkClick = () => {
|
||||
setIsDrawerOpen(false)
|
||||
}
|
||||
@ -156,11 +174,13 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
/>
|
||||
<ShellAppBar
|
||||
doShowPeers={doShowPeers}
|
||||
handleDrawerOpen={handleDrawerOpen}
|
||||
handleLinkButtonClick={handleLinkButtonClick}
|
||||
onDrawerOpen={handleDrawerOpen}
|
||||
onLinkButtonClick={handleLinkButtonClick}
|
||||
isDrawerOpen={isDrawerOpen}
|
||||
isPeerListOpen={isPeerListOpen}
|
||||
numberOfPeers={numberOfPeers}
|
||||
title={title}
|
||||
onPeerListOpen={handlePeerListOpen}
|
||||
/>
|
||||
<Drawer
|
||||
isDrawerOpen={isDrawerOpen}
|
||||
@ -171,10 +191,18 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
theme={theme}
|
||||
userPeerId={userPeerId}
|
||||
/>
|
||||
|
||||
<RouteContent isDrawerOpen={isDrawerOpen}>
|
||||
<RouteContent
|
||||
isDrawerOpen={isDrawerOpen}
|
||||
isPeerListOpen={isPeerListOpen}
|
||||
>
|
||||
<ErrorBoundary>{children}</ErrorBoundary>
|
||||
</RouteContent>
|
||||
<PeerList
|
||||
userId={userPeerId}
|
||||
isPeerListOpen={isPeerListOpen}
|
||||
onPeerListClose={handlePeerListClose}
|
||||
peerList={peerList}
|
||||
/>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
</ShellContext.Provider>
|
||||
|
@ -9,19 +9,22 @@ import MenuIcon from '@mui/icons-material/Menu'
|
||||
import LinkIcon from '@mui/icons-material/Link'
|
||||
|
||||
import { drawerWidth } from './Drawer'
|
||||
import { peerListWidth } from './PeerList'
|
||||
|
||||
interface AppBarProps extends MuiAppBarProps {
|
||||
open?: boolean
|
||||
isDrawerOpen?: boolean
|
||||
isPeerListOpen?: boolean
|
||||
}
|
||||
|
||||
export const AppBar = styled(MuiAppBar, {
|
||||
shouldForwardProp: prop => prop !== 'open',
|
||||
})<AppBarProps>(({ theme, open }) => ({
|
||||
shouldForwardProp: prop =>
|
||||
prop !== 'isDrawerOpen' && prop !== 'isPeerListOpen',
|
||||
})<AppBarProps>(({ theme, isDrawerOpen, isPeerListOpen }) => ({
|
||||
transition: theme.transitions.create(['margin', 'width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
...(open && {
|
||||
...(isDrawerOpen && {
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
marginLeft: `${drawerWidth}px`,
|
||||
transition: theme.transitions.create(['margin', 'width'], {
|
||||
@ -29,27 +32,43 @@ export const AppBar = styled(MuiAppBar, {
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
}),
|
||||
...(isPeerListOpen && {
|
||||
width: `calc(100% - ${peerListWidth}px)`,
|
||||
marginRight: `${peerListWidth}px`,
|
||||
transition: theme.transitions.create(['margin', 'width'], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
interface ShellAppBarProps {
|
||||
doShowPeers: boolean
|
||||
handleDrawerOpen: () => void
|
||||
handleLinkButtonClick: () => Promise<void>
|
||||
onDrawerOpen: () => void
|
||||
onLinkButtonClick: () => Promise<void>
|
||||
isDrawerOpen: boolean
|
||||
isPeerListOpen: boolean
|
||||
numberOfPeers: number
|
||||
title: string
|
||||
onPeerListOpen: () => void
|
||||
}
|
||||
|
||||
export const ShellAppBar = ({
|
||||
doShowPeers,
|
||||
handleDrawerOpen,
|
||||
handleLinkButtonClick,
|
||||
onDrawerOpen,
|
||||
onLinkButtonClick,
|
||||
isDrawerOpen,
|
||||
isPeerListOpen,
|
||||
numberOfPeers,
|
||||
title,
|
||||
onPeerListOpen,
|
||||
}: ShellAppBarProps) => {
|
||||
return (
|
||||
<AppBar position="fixed" open={isDrawerOpen}>
|
||||
<AppBar
|
||||
position="fixed"
|
||||
isDrawerOpen={isDrawerOpen}
|
||||
isPeerListOpen={isPeerListOpen}
|
||||
>
|
||||
<Toolbar
|
||||
variant="regular"
|
||||
sx={{
|
||||
@ -64,7 +83,7 @@ export const ShellAppBar = ({
|
||||
color="inherit"
|
||||
aria-label="Open menu"
|
||||
sx={{ mr: 2, ...(isDrawerOpen && { display: 'none' }) }}
|
||||
onClick={handleDrawerOpen}
|
||||
onClick={onDrawerOpen}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
@ -83,14 +102,18 @@ export const ShellAppBar = ({
|
||||
color="inherit"
|
||||
aria-label="Copy current URL"
|
||||
sx={{ ml: 'auto' }}
|
||||
onClick={handleLinkButtonClick}
|
||||
onClick={onLinkButtonClick}
|
||||
>
|
||||
<LinkIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{doShowPeers ? (
|
||||
<Tooltip title="Number of peers in the room">
|
||||
<StepIcon icon={numberOfPeers} sx={{ ml: 2 }} />
|
||||
<Tooltip title="Click to show peer list">
|
||||
<StepIcon
|
||||
icon={numberOfPeers}
|
||||
onClick={onPeerListOpen}
|
||||
sx={{ ml: 2 }}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</Toolbar>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createContext, Dispatch, SetStateAction } from 'react'
|
||||
|
||||
import { AlertOptions } from 'models/shell'
|
||||
import { Peer } from 'models/chat'
|
||||
|
||||
interface ShellContextProps {
|
||||
numberOfPeers: number
|
||||
@ -9,6 +10,10 @@ interface ShellContextProps {
|
||||
setNumberOfPeers: Dispatch<SetStateAction<number>>
|
||||
setTitle: Dispatch<SetStateAction<string>>
|
||||
showAlert: (message: string, options?: AlertOptions) => void
|
||||
isPeerListOpen: boolean
|
||||
setIsPeerListOpen: Dispatch<SetStateAction<boolean>>
|
||||
peerList: Peer[]
|
||||
setPeerList: Dispatch<SetStateAction<Peer[]>>
|
||||
}
|
||||
|
||||
export const ShellContext = createContext<ShellContextProps>({
|
||||
@ -18,4 +23,8 @@ export const ShellContext = createContext<ShellContextProps>({
|
||||
setNumberOfPeers: () => {},
|
||||
setTitle: () => {},
|
||||
showAlert: () => {},
|
||||
isPeerListOpen: false,
|
||||
setIsPeerListOpen: () => {},
|
||||
peerList: [],
|
||||
setPeerList: () => {},
|
||||
})
|
||||
|
@ -5,6 +5,11 @@ export interface UnsentMessage {
|
||||
authorId: string
|
||||
}
|
||||
|
||||
export interface Peer {
|
||||
peerId: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface ReceivedMessage extends UnsentMessage {
|
||||
timeReceived: number
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export enum PeerActions {
|
||||
MESSAGE = 'MESSAGE',
|
||||
PEER_NAME = 'PEER_NAME',
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user