handle more messages
This commit is contained in:
parent
f40ad116fe
commit
deb55c2c6d
5 changed files with 89 additions and 33 deletions
|
@ -1,5 +1,5 @@
|
||||||
import { AppShell, Box, Button, Grid, Group, Input, Stack, TextInput } from "@mantine/core"
|
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 { useDispatch, useSelector } from "react-redux"
|
||||||
import ChannelList from "./components/ChannelList"
|
import ChannelList from "./components/ChannelList"
|
||||||
import Header from "./components/Header"
|
import Header from "./components/Header"
|
||||||
|
@ -12,16 +12,23 @@ function App() {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const isConnected = useSelector((state: RootState) => state.chat.isConnected);
|
||||||
const currentChannel = useSelector((state: RootState) => state.chat.currentChannel);
|
const currentChannel = useSelector((state: RootState) => state.chat.currentChannel);
|
||||||
const messages = useSelector((state: RootState) => state.chat.messages);
|
const messages = useSelector((state: RootState) => state.chat.messages);
|
||||||
|
|
||||||
const chatDisabled = currentChannel === null;
|
const messagesEndRef = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const chatDisabled = currentChannel === null || !isConnected;
|
||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
dispatch(chatActions.sendMessage({ content: message }));
|
dispatch(chatActions.sendMessage({ content: message }));
|
||||||
setMessage("");
|
setMessage("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
messagesEndRef.current?.scrollIntoView();
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
padding={0}
|
padding={0}
|
||||||
|
@ -30,13 +37,14 @@ function App() {
|
||||||
header={<Header opened={opened} setOpened={setOpened} />}
|
header={<Header opened={opened} setOpened={setOpened} />}
|
||||||
aside={<MemberList />}
|
aside={<MemberList />}
|
||||||
>
|
>
|
||||||
<Stack sx={{ height: "calc(100vh - 60px)" }} spacing={0}>
|
<Stack sx={{ height: "calc(100vh - 60px)", maxHeight: "calc(100vh - 60px)" }} spacing={0}>
|
||||||
<Stack sx={{ overflowY: "auto", flexGrow: 1, flexShrink: 0 }} p="md">
|
<Stack sx={{ overflowY: "scroll", flexGrow: 1 }} p="md">
|
||||||
{messages.map((msg, i) => (
|
{messages.map((msg, i) => (
|
||||||
<Message key={`msg${i}`} message={msg} />
|
<Message key={`msg${i}`} message={msg} />
|
||||||
))}
|
))}
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
</Stack>
|
</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) => {
|
<TextInput sx={{ flexGrow: 1 }} placeholder="Message" disabled={chatDisabled} value={message} onChange={(event) => setMessage(event.target.value)} onKeyUp={(event) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
sendMessage();
|
sendMessage();
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { Header as MHeader, Group, Text, MediaQuery, Burger, createStyles } from '@mantine/core';
|
import { Header as MHeader, Group, Text, MediaQuery, Burger, createStyles } from '@mantine/core';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { RootState } from '../store';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
opened: boolean
|
opened: boolean
|
||||||
|
@ -18,6 +20,7 @@ const useStyles = createStyles((theme) => ({
|
||||||
|
|
||||||
function Header(props: HeaderProps) {
|
function Header(props: HeaderProps) {
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
|
const channelName = useSelector((state: RootState) => state.chat.currentChannel);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MHeader height={60} p="md" sx={(theme) => ({
|
<MHeader height={60} p="md" sx={(theme) => ({
|
||||||
|
@ -34,7 +37,7 @@ function Header(props: HeaderProps) {
|
||||||
</MediaQuery>
|
</MediaQuery>
|
||||||
<Text size="xl" my="auto" className={classes.title}>Echo Web</Text>
|
<Text size="xl" my="auto" className={classes.title}>Echo Web</Text>
|
||||||
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
|
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
|
||||||
<Text size="md" my="auto">Channel 1</Text>
|
<Text size="md" my="auto">{channelName}</Text>
|
||||||
</MediaQuery>
|
</MediaQuery>
|
||||||
</Group>
|
</Group>
|
||||||
</MHeader>
|
</MHeader>
|
||||||
|
|
|
@ -7,19 +7,16 @@ interface MessageProps {
|
||||||
|
|
||||||
function Message(props: MessageProps) {
|
function Message(props: MessageProps) {
|
||||||
return (
|
return (
|
||||||
<Group sx={{ flexWrap: "nowrap" }}>
|
<Stack spacing={0} sx={{ flexGrow: 1, flexShrink: 1 }}>
|
||||||
<Avatar color={props.message.color} radius="xl" sx={{ flexGrow: 0, flexShrink: 0, marginBottom: "auto" }}>LS</Avatar>
|
<Text>
|
||||||
<Stack spacing="xs" sx={{ flexGrow: 1, flexShrink: 1 }}>
|
<Text component="span" color={props.message.color}>{props.message.author}</Text>
|
||||||
<Text>
|
{' '}
|
||||||
{props.message.author}
|
<Text component="span" size="sm" my="auto" color="dimmed">at {props.message.date.toLocaleString()}</Text>
|
||||||
{' '}
|
</Text>
|
||||||
<Text component="span" size="sm" my="auto" color="dimmed">at {props.message.date}</Text>
|
<Text sx={{ whiteSpace: "pre-wrap" }}>
|
||||||
</Text>
|
{props.message.content}
|
||||||
<Text>
|
</Text>
|
||||||
{props.message.content}
|
</Stack>
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
</Group>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,8 @@ enum MessageType {
|
||||||
ResCommandData = "commandData",
|
ResCommandData = "commandData",
|
||||||
ResChannelHistory = "channelHistory",
|
ResChannelHistory = "channelHistory",
|
||||||
ResErrorOccurred = "errorOccured",
|
ResErrorOccurred = "errorOccured",
|
||||||
ResAdditionalHistory = "additionalHistory"
|
ResAdditionalHistory = "additionalHistory",
|
||||||
|
ResUserlistUpdate = "userlistUpdate"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServerMessage {
|
interface ServerMessage {
|
||||||
|
@ -37,7 +38,7 @@ export interface ChatMessage {
|
||||||
content: string;
|
content: string;
|
||||||
author: string;
|
author: string;
|
||||||
color: string;
|
color: string;
|
||||||
date: number;
|
date: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultMetadata = ['Unknown', '#ffffff', '0'];
|
const defaultMetadata = ['Unknown', '#ffffff', '0'];
|
||||||
|
@ -84,34 +85,63 @@ const chatMiddleware: Middleware = (store) => {
|
||||||
case MessageType.ResServerData:
|
case MessageType.ResServerData:
|
||||||
store.dispatch(chatActions.updateChannels({ channels: JSON.parse(msg.data[0]) }));
|
store.dispatch(chatActions.updateChannels({ channels: JSON.parse(msg.data[0]) }));
|
||||||
store.dispatch(chatActions.updateMotd({ motd: msg.data[1] }));
|
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];
|
return user[0];
|
||||||
})}));
|
})}));
|
||||||
break;
|
break;
|
||||||
case MessageType.ResOutboundMessage:
|
case MessageType.ResOutboundMessage:
|
||||||
let metadata = msg.metadata || defaultMetadata;
|
let outboundMessageMetadata = msg.metadata || defaultMetadata;
|
||||||
store.dispatch(chatActions.recieveMessage({ message: {
|
store.dispatch(chatActions.recieveMessage({ message: {
|
||||||
author: metadata[0],
|
author: outboundMessageMetadata[0],
|
||||||
content: msg.data,
|
content: msg.data,
|
||||||
color: metadata[1],
|
color: outboundMessageMetadata[1],
|
||||||
date: 0
|
date: (new Date(parseInt(outboundMessageMetadata[2]) * 1000)).toLocaleString()
|
||||||
}}))
|
}}))
|
||||||
break;
|
break;
|
||||||
case MessageType.ResCommandData:
|
case MessageType.ResCommandData:
|
||||||
metadata = msg.metadata || defaultMetadata;
|
let commandDataMetadata = msg.metadata || defaultMetadata;
|
||||||
store.dispatch(chatActions.recieveMessage({ message: {
|
store.dispatch(chatActions.recieveMessage({ message: {
|
||||||
author: metadata[0],
|
author: commandDataMetadata[0],
|
||||||
content: msg.data,
|
content: msg.data.join("\n"),
|
||||||
color: metadata[1],
|
color: commandDataMetadata[1],
|
||||||
date: 0
|
date: (new Date(parseInt(commandDataMetadata[2]) * 1000)).toLocaleString()
|
||||||
}}));
|
}}));
|
||||||
break;
|
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:
|
default:
|
||||||
store.dispatch(chatActions.recieveMessage({ message: {
|
store.dispatch(chatActions.recieveMessage({ message: {
|
||||||
author: 'Server',
|
author: 'Server',
|
||||||
content: JSON.stringify(msg),
|
content: JSON.stringify(msg),
|
||||||
color: '#ff0000',
|
color: '#ff0000',
|
||||||
date: 0
|
date: (new Date()).toLocaleString()
|
||||||
}}));
|
}}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,17 +56,35 @@ const chatSlice = createSlice({
|
||||||
}>) => {
|
}>) => {
|
||||||
state.motd = action.payload.motd;
|
state.motd = action.payload.motd;
|
||||||
},
|
},
|
||||||
updateUsers: (state, action: PayloadAction<{
|
setUsers: (state, action: PayloadAction<{
|
||||||
users: string[]
|
users: string[]
|
||||||
}>) => {
|
}>) => {
|
||||||
state.users = action.payload.users;
|
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<{
|
changeChannel: (state, action: PayloadAction<{
|
||||||
channel: string
|
channel: string
|
||||||
}>) => {
|
}>) => {
|
||||||
state.currentChannel = action.payload.channel;
|
state.currentChannel = action.payload.channel;
|
||||||
state.messages = [];
|
state.messages = [];
|
||||||
},
|
},
|
||||||
|
addMessages: (state, action: PayloadAction<{
|
||||||
|
messages: ChatMessage[]
|
||||||
|
}>) => {
|
||||||
|
state.messages.push(...action.payload.messages);
|
||||||
|
},
|
||||||
sendMessage: (state, action: PayloadAction<{
|
sendMessage: (state, action: PayloadAction<{
|
||||||
content: string
|
content: string
|
||||||
}>) => {}
|
}>) => {}
|
||||||
|
|
Loading…
Reference in a new issue