From 74b2542e8b2cb001d7de0b1d26fa6427df178129 Mon Sep 17 00:00:00 2001 From: Jeremy Kahn Date: Tue, 19 Nov 2024 21:28:19 -0600 Subject: [PATCH] feat(#141): [wip] render direct message room --- .../ChatTranscript/ChatTranscript.tsx | 37 ++++++----- src/components/Room/Room.test.tsx | 17 +++-- src/components/Room/Room.tsx | 65 +++++++++++-------- src/components/Room/useRoom.ts | 26 ++++++-- src/components/Shell/PeerListItem.tsx | 17 ++++- src/components/Shell/Shell.tsx | 29 ++++++++- src/contexts/ShellContext.ts | 13 ++++ 7 files changed, 147 insertions(+), 57 deletions(-) diff --git a/src/components/ChatTranscript/ChatTranscript.tsx b/src/components/ChatTranscript/ChatTranscript.tsx index 46e026b20..cf1298049 100644 --- a/src/components/ChatTranscript/ChatTranscript.tsx +++ b/src/components/ChatTranscript/ChatTranscript.tsx @@ -1,17 +1,21 @@ -import { HTMLAttributes, useRef, useEffect, useState, useContext } from 'react' -import Box from '@mui/material/Box' +import { useRef, useEffect, useState, useContext } from 'react' +import Box, { BoxProps } from '@mui/material/Box' import useTheme from '@mui/material/styles/useTheme' import { Message as IMessage, InlineMedia } from 'models/chat' import { Message } from 'components/Message' import { ShellContext } from 'contexts/ShellContext' -export interface ChatTranscriptProps extends HTMLAttributes { +export interface ChatTranscriptProps extends BoxProps { messageLog: Array userId: string } -export const ChatTranscript = ({ messageLog, userId }: ChatTranscriptProps) => { +export const ChatTranscript = ({ + messageLog, + userId, + sx, +}: ChatTranscriptProps) => { const { showRoomControls } = useContext(ShellContext) const theme = useTheme() const boxRef = useRef(null) @@ -62,17 +66,20 @@ export const ChatTranscript = ({ messageLog, userId }: ChatTranscriptProps) => { {messageLog.map((message, idx) => { const previousMessage = messageLog[idx - 1] diff --git a/src/components/Room/Room.test.tsx b/src/components/Room/Room.test.tsx index 3f3fcbf4e..f85e44d4d 100644 --- a/src/components/Room/Room.test.tsx +++ b/src/components/Room/Room.test.tsx @@ -119,7 +119,7 @@ describe('Room', () => { expect(textInput).toHaveValue('') }) - test('message is sent to peer', async () => { + test('message is sent to peers', async () => { render( { await userEvent.type(textInput, 'hello') await userEvent.click(sendButton) - expect(mockMessagedSender).toHaveBeenCalledWith({ - authorId: mockUserId, - text: 'hello', - timeSent: mockNowTime, - id: 'abc123', - }) + expect(mockMessagedSender).toHaveBeenCalledWith( + { + authorId: mockUserId, + text: 'hello', + timeSent: mockNowTime, + id: 'abc123', + }, + null + ) }) }) diff --git a/src/components/Room/Room.tsx b/src/components/Room/Room.tsx index b67be80f4..306a464e5 100644 --- a/src/components/Room/Room.tsx +++ b/src/components/Room/Room.tsx @@ -33,6 +33,7 @@ export interface RoomProps { userId: string encryptionService?: typeof encryption timeService?: typeof time + targetPeerId?: string } export function Room({ @@ -43,6 +44,7 @@ export function Room({ roomId, password, userId, + targetPeerId, }: RoomProps) { const theme = useTheme() const settingsContext = useContext(SettingsContext) @@ -72,9 +74,12 @@ export function Room({ publicKey, encryptionService, timeService, + targetPeerId, } ) + const isDirectMessageRoom = typeof targetPeerId === 'string' + const handleMessageSubmit = async (message: string) => { await sendMessage(message) } @@ -105,32 +110,34 @@ export function Room({ overflow: 'auto', }} > - - - - - - - - - - - - - + {!isDirectMessageRoom && ( + + + + + + + + + + + + + + )} - + { @@ -58,6 +59,7 @@ export function useRoom( roomId, userId, publicKey, + targetPeerId = null, getUuid = uuid, encryptionService = encryption, timeService = time, @@ -65,6 +67,8 @@ export function useRoom( ) { const isPrivate = password !== undefined + const isDirectMessageRoom = typeof targetPeerId === 'string' + const { peerList, setPeerList, @@ -76,6 +80,8 @@ export function useRoom( customUsername, updatePeer, peerRoomRef, + messageLog, + setMessageLog: shellSetMessageLog, } = useContext(ShellContext) const [peerRoom] = useState( @@ -89,9 +95,6 @@ export function useRoom( const settingsContext = useContext(SettingsContext) const { showActiveTypingStatus } = settingsContext.getUserSettings() const [isMessageSending, setIsMessageSending] = useState(false) - const [messageLog, _setMessageLog] = useState>( - [] - ) const [newMessageAudio] = useState(() => new Audio('/sounds/new-message.aac')) const { getDisplayUsername } = usePeerNameDisplay() @@ -113,7 +116,7 @@ export function useRoom( } } - _setMessageLog(messages.slice(-messageTranscriptSizeLimit)) + shellSetMessageLog(messages.slice(-messageTranscriptSizeLimit)) } const [isShowingMessages, setIsShowingMessages] = useState(true) @@ -191,12 +194,22 @@ export function useRoom( useEffect(() => { return () => { + if (isDirectMessageRoom) return + sendTypingStatusChange({ isTyping: false }) peerRoom.leaveRoom() peerRoomRef.current = null setPeerList([]) + shellSetMessageLog([]) } - }, [peerRoom, setPeerList, sendTypingStatusChange, peerRoomRef]) + }, [ + peerRoom, + setPeerList, + sendTypingStatusChange, + peerRoomRef, + isDirectMessageRoom, + shellSetMessageLog, + ]) useEffect(() => { setPassword(password) @@ -252,7 +265,8 @@ export function useRoom( setIsTyping(false) setIsMessageSending(true) setMessageLog([...messageLog, unsentMessage]) - await sendPeerMessage(unsentMessage) + + await sendPeerMessage(unsentMessage, targetPeerId) setMessageLog([ ...messageLog, diff --git a/src/components/Shell/PeerListItem.tsx b/src/components/Shell/PeerListItem.tsx index d049072fa..e02261e45 100644 --- a/src/components/Shell/PeerListItem.tsx +++ b/src/components/Shell/PeerListItem.tsx @@ -16,11 +16,13 @@ import ListItem from '@mui/material/ListItem' import ListItemText from '@mui/material/ListItemText' import Tooltip from '@mui/material/Tooltip' import Typography from '@mui/material/Typography' -import { useState } from 'react' +import useTheme from '@mui/material/styles/useTheme' +import { useContext, useState } from 'react' import { AudioVolume } from 'components/AudioVolume' import { PeerNameDisplay } from 'components/PeerNameDisplay' import { PublicKey } from 'components/PublicKey' +import { Room } from 'components/Room' import { PeerConnectionType } from 'lib/PeerRoom' import { AudioChannel, @@ -28,6 +30,7 @@ import { Peer, PeerVerificationState, } from 'models/chat' +import { SettingsContext } from 'contexts/SettingsContext' import { PeerDownloadFileButton } from './PeerDownloadFileButton' @@ -62,6 +65,9 @@ export const PeerListItem = ({ peerConnectionTypes, peerAudioChannels, }: PeerListItemProps) => { + const theme = useTheme() + const { getUserSettings } = useContext(SettingsContext) + const { userId } = getUserSettings() const [showPeerDialog, setShowPeerDialog] = useState(false) const hasPeerConnection = peer.peerId in peerConnectionTypes @@ -169,6 +175,15 @@ export const PeerListItem = ({ + + + diff --git a/src/components/Shell/Shell.tsx b/src/components/Shell/Shell.tsx index 0100d72f9..af5d9237f 100644 --- a/src/components/Shell/Shell.tsx +++ b/src/components/Shell/Shell.tsx @@ -17,7 +17,11 @@ import MuiDrawer from '@mui/material/Drawer' import Link from '@mui/material/Link' import { useWindowSize } from '@react-hook/window-size' -import { ShellContext } from 'contexts/ShellContext' +import { + MessageLog, + ShellContext, + ShellMessageLog, +} from 'contexts/ShellContext' import { SettingsContext } from 'contexts/SettingsContext' import { AlertOptions, QueryParamKeys } from 'models/shell' import { @@ -103,6 +107,25 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { Record >({}) + const [shellMessageLog, setShellMessageLog] = useState({ + groupMessageLog: [], + directMessageLog: {}, + }) + + const { groupMessageLog: messageLog } = shellMessageLog + + // FIXME: Store direct messages + const setMessageLog = useCallback((messageLog: MessageLog) => { + setShellMessageLog(() => { + const shellMessageLog: ShellMessageLog = { + groupMessageLog: messageLog, + directMessageLog: {}, + } + + return shellMessageLog + }) + }, []) + const showAlert = useCallback((message: string, options?: AlertOptions) => { setAlertText(message) setAlertSeverity(options?.severity ?? 'info') @@ -162,6 +185,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { connectionTestResults, updatePeer, peerRoomRef, + messageLog, + setMessageLog, }), [ isEmbedded, @@ -193,6 +218,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { connectionTestResults, updatePeer, peerRoomRef, + messageLog, + setMessageLog, ] ) diff --git a/src/contexts/ShellContext.ts b/src/contexts/ShellContext.ts index 9e4f0be5b..b1af2314e 100644 --- a/src/contexts/ShellContext.ts +++ b/src/contexts/ShellContext.ts @@ -12,6 +12,8 @@ import { AudioChannel, AudioChannelName, AudioState, + InlineMedia, + Message, Peer, PeerAudioChannelState, ScreenShareState, @@ -19,6 +21,13 @@ import { } from 'models/chat' import { AlertOptions } from 'models/shell' +export type MessageLog = (Message | InlineMedia)[] + +export interface ShellMessageLog { + groupMessageLog: MessageLog + directMessageLog: Record +} + interface ShellContextProps { isEmbedded: boolean tabHasFocus: boolean @@ -53,6 +62,8 @@ interface ShellContextProps { connectionTestResults: ConnectionTestResults updatePeer: (peerId: string, updatedProperties: Partial) => void peerRoomRef: MutableRefObject + messageLog: MessageLog + setMessageLog: (messageLog: MessageLog) => void } export const ShellContext = createContext({ @@ -94,4 +105,6 @@ export const ShellContext = createContext({ }, updatePeer: () => {}, peerRoomRef: { current: null }, + messageLog: [], + setMessageLog: () => {}, })