handle more messages

This commit is contained in:
Jake Walker 2022-08-09 14:51:09 +01:00
parent f40ad116fe
commit deb55c2c6d
5 changed files with 89 additions and 33 deletions

View file

@ -1,5 +1,5 @@
import { AppShell, Box, Button, Grid, Group, Input, Stack, TextInput } from "@mantine/core"
import { useState } from "react"
import { useState, createRef, useEffect } from "react"
import { useDispatch, useSelector } from "react-redux"
import ChannelList from "./components/ChannelList"
import Header from "./components/Header"
@ -12,16 +12,23 @@ function App() {
const [opened, setOpened] = useState(false);
const [message, setMessage] = useState("");
const dispatch = useDispatch();
const isConnected = useSelector((state: RootState) => state.chat.isConnected);
const currentChannel = useSelector((state: RootState) => state.chat.currentChannel);
const messages = useSelector((state: RootState) => state.chat.messages);
const chatDisabled = currentChannel === null;
const messagesEndRef = createRef<HTMLDivElement>();
const chatDisabled = currentChannel === null || !isConnected;
const sendMessage = () => {
dispatch(chatActions.sendMessage({ content: message }));
setMessage("");
}
useEffect(() => {
messagesEndRef.current?.scrollIntoView();
}, [messages]);
return (
<AppShell
padding={0}
@ -30,13 +37,14 @@ function App() {
header={<Header opened={opened} setOpened={setOpened} />}
aside={<MemberList />}
>
<Stack sx={{ height: "calc(100vh - 60px)" }} spacing={0}>
<Stack sx={{ overflowY: "auto", flexGrow: 1, flexShrink: 0 }} p="md">
<Stack sx={{ height: "calc(100vh - 60px)", maxHeight: "calc(100vh - 60px)" }} spacing={0}>
<Stack sx={{ overflowY: "scroll", flexGrow: 1 }} p="md">
{messages.map((msg, i) => (
<Message key={`msg${i}`} message={msg} />
))}
<div ref={messagesEndRef} />
</Stack>
<Group p="md" sx={(theme) => ({ borderTop: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]}`, flexGrow: 0, flexShrink: 0 })}>
<Group p="md" sx={(theme) => ({ borderTop: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]}` })}>
<TextInput sx={{ flexGrow: 1 }} placeholder="Message" disabled={chatDisabled} value={message} onChange={(event) => setMessage(event.target.value)} onKeyUp={(event) => {
if (event.key === "Enter") {
sendMessage();

View file

@ -1,4 +1,6 @@
import { Header as MHeader, Group, Text, MediaQuery, Burger, createStyles } from '@mantine/core';
import { useSelector } from 'react-redux';
import { RootState } from '../store';
interface HeaderProps {
opened: boolean
@ -18,6 +20,7 @@ const useStyles = createStyles((theme) => ({
function Header(props: HeaderProps) {
const { classes } = useStyles();
const channelName = useSelector((state: RootState) => state.chat.currentChannel);
return (
<MHeader height={60} p="md" sx={(theme) => ({
@ -34,7 +37,7 @@ function Header(props: HeaderProps) {
</MediaQuery>
<Text size="xl" my="auto" className={classes.title}>Echo Web</Text>
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
<Text size="md" my="auto">Channel 1</Text>
<Text size="md" my="auto">{channelName}</Text>
</MediaQuery>
</Group>
</MHeader>

View file

@ -7,19 +7,16 @@ interface MessageProps {
function Message(props: MessageProps) {
return (
<Group sx={{ flexWrap: "nowrap" }}>
<Avatar color={props.message.color} radius="xl" sx={{ flexGrow: 0, flexShrink: 0, marginBottom: "auto" }}>LS</Avatar>
<Stack spacing="xs" sx={{ flexGrow: 1, flexShrink: 1 }}>
<Stack spacing={0} sx={{ flexGrow: 1, flexShrink: 1 }}>
<Text>
{props.message.author}
<Text component="span" color={props.message.color}>{props.message.author}</Text>
{' '}
<Text component="span" size="sm" my="auto" color="dimmed">at {props.message.date}</Text>
<Text component="span" size="sm" my="auto" color="dimmed">at {props.message.date.toLocaleString()}</Text>
</Text>
<Text>
<Text sx={{ whiteSpace: "pre-wrap" }}>
{props.message.content}
</Text>
</Stack>
</Group>
)
}

View file

@ -22,7 +22,8 @@ enum MessageType {
ResCommandData = "commandData",
ResChannelHistory = "channelHistory",
ResErrorOccurred = "errorOccured",
ResAdditionalHistory = "additionalHistory"
ResAdditionalHistory = "additionalHistory",
ResUserlistUpdate = "userlistUpdate"
}
interface ServerMessage {
@ -37,7 +38,7 @@ export interface ChatMessage {
content: string;
author: string;
color: string;
date: number;
date: string;
}
const defaultMetadata = ['Unknown', '#ffffff', '0'];
@ -84,34 +85,63 @@ const chatMiddleware: Middleware = (store) => {
case MessageType.ResServerData:
store.dispatch(chatActions.updateChannels({ channels: JSON.parse(msg.data[0]) }));
store.dispatch(chatActions.updateMotd({ motd: msg.data[1] }));
store.dispatch(chatActions.updateUsers({ users: JSON.parse(msg.data[2]).map((user) => {
store.dispatch(chatActions.setUsers({ users: JSON.parse(msg.data[2]).map((user: any) => {
return user[0];
})}));
break;
case MessageType.ResOutboundMessage:
let metadata = msg.metadata || defaultMetadata;
let outboundMessageMetadata = msg.metadata || defaultMetadata;
store.dispatch(chatActions.recieveMessage({ message: {
author: metadata[0],
author: outboundMessageMetadata[0],
content: msg.data,
color: metadata[1],
date: 0
color: outboundMessageMetadata[1],
date: (new Date(parseInt(outboundMessageMetadata[2]) * 1000)).toLocaleString()
}}))
break;
case MessageType.ResCommandData:
metadata = msg.metadata || defaultMetadata;
let commandDataMetadata = msg.metadata || defaultMetadata;
store.dispatch(chatActions.recieveMessage({ message: {
author: metadata[0],
content: msg.data,
color: metadata[1],
date: 0
author: commandDataMetadata[0],
content: msg.data.join("\n"),
color: commandDataMetadata[1],
date: (new Date(parseInt(commandDataMetadata[2]) * 1000)).toLocaleString()
}}));
break;
case MessageType.ResChannelHistory:
store.dispatch(chatActions.addMessages({
messages: msg.data.map((message: any) => ({
author: message[0],
content: message[3],
color: message[4],
date: (new Date(parseInt(message[5]) * 1000)).toLocaleString()
}))
}))
break;
case MessageType.ResChannelUpdate:
const [user, fromChannel, toChannel] = msg.data;
store.dispatch(chatActions.updateUsers({
user,
fromChannel,
toChannel
}));
break;
case MessageType.ResConnectionTerminated:
store.dispatch(chatActions.recieveMessage({ message: {
author: "Server",
content: msg.data,
color: '#ff0000',
date: (new Date()).toLocaleString()
}}));
socket?.close();
break;
case MessageType.ResUserlistUpdate:
break;
default:
store.dispatch(chatActions.recieveMessage({ message: {
author: 'Server',
content: JSON.stringify(msg),
color: '#ff0000',
date: 0
date: (new Date()).toLocaleString()
}}));
}
}

View file

@ -56,17 +56,35 @@ const chatSlice = createSlice({
}>) => {
state.motd = action.payload.motd;
},
updateUsers: (state, action: PayloadAction<{
setUsers: (state, action: PayloadAction<{
users: string[]
}>) => {
state.users = action.payload.users;
},
updateUsers: (state, action: PayloadAction<{
user: string,
fromChannel: string,
toChannel: string
}>) => {
if (action.payload.toChannel === state.currentChannel) {
if (!state.users.includes(action.payload.user)) {
state.users.push(action.payload.user);
}
} else {
state.users = state.users.filter((user) => user !== action.payload.user);
}
},
changeChannel: (state, action: PayloadAction<{
channel: string
}>) => {
state.currentChannel = action.payload.channel;
state.messages = [];
},
addMessages: (state, action: PayloadAction<{
messages: ChatMessage[]
}>) => {
state.messages.push(...action.payload.messages);
},
sendMessage: (state, action: PayloadAction<{
content: string
}>) => {}