Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Message Functionalities #7

Merged
merged 6 commits into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ yarn start
- [x] Real-time Subscription for messages within each window
- [x] Retrieval of messages of each chat with pagination
- [x] Message Parsing System (Emojis/Mentions/Markdown/Paragraphs/...etc)
- [ ] Main Chat Window Functionalities (Send/Delete/Edit/Star/...etc) Messages
- [x] Main Chat Window Functionalities (Send/Delete/Edit/...etc) Messages

### Screenshots

Expand Down
37 changes: 29 additions & 8 deletions src/components/ChatWindow/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ const HeaderFooterContainer = styled.div`
height: 4rem;
`

interface MessagesMap {
[_id: string]: RealtimeAPIMessage
}

function ChatWindow() {
const { id } = useParams();
const [messages, setMessages] = useState<RealtimeAPIMessage[]>([]);
const [messages, setMessages] = useState<MessagesMap>({});
const [messageToEdit, setMessageToEdit] = useState<RealtimeAPIMessage | null>(null);


const loginToRoom = async () => {
Expand All @@ -49,22 +53,39 @@ function ChatWindow() {

const showMessages = async () => {
let lastMessageDate = null;
if(messages.length){
lastMessageDate = messages[0].ts;
let messagesKeys = Object.keys(messages);
if(messagesKeys.length){
lastMessageDate = messages[ messagesKeys[0] ].ts;
}

let newMessages: RealtimeAPIMessage[] = await loadMessagesFromRoom(id, 10, lastMessageDate);
setMessages((oldMessages) => [...newMessages, ...oldMessages]);
let newMessages: RealtimeAPIMessage[] = await loadMessagesFromRoom(id, 5, lastMessageDate);

setMessages((oldMessages) => {
let toBeMessages: MessagesMap = {};
newMessages.map((message) => {
toBeMessages[message._id] = message;
});
toBeMessages = {...toBeMessages, ...oldMessages};
return toBeMessages;
});
}

const addMessage = async (message: RealtimeAPIMessage) => {
setMessages((oldMessages) => [...oldMessages, message]);
setMessages((oldMessages) => {
let toBeMessages = {...oldMessages};
toBeMessages[message._id] = message;
return toBeMessages;
});
}

const loadMoreMessages = async () => {
showMessages();
}

const onEditMessageAction = (message: RealtimeAPIMessage) => {
setMessageToEdit(message);
}

useEffect(() => {
loginToRoom();
}, []);
Expand All @@ -75,9 +96,9 @@ function ChatWindow() {
<HeaderFooterContainer>
<Header />
</HeaderFooterContainer>
<MessageList messages={messages} loadMoreMessages={loadMoreMessages} />
<MessageList messages={messages} loadMoreMessages={loadMoreMessages} onEditMessageAction={onEditMessageAction} />
<HeaderFooterContainer>
<MessageForm />
<MessageForm messageToEdit={messageToEdit} setMessageToEdit={setMessageToEdit} />
</HeaderFooterContainer>
</Container>
);
Expand Down
45 changes: 41 additions & 4 deletions src/components/ChatWindow/MessageForm/MessageForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React, { useState, useEffect } from "react";
import { hot } from "react-hot-loader/root";
import styled from "styled-components"
import { sendTextMessage, editTextMessage } from "../../../util/message.util";
import { useParams } from "react-router-dom";
import { RealtimeAPIMessage } from '../../../interfaces/message';

const Container = styled.div`
position: fixed;
Expand All @@ -25,11 +28,45 @@ const TextInput = styled.textarea`
`


function MessageForm() {
function MessageForm(props: any) {
const { id: roomId } = useParams();
const [message, setMessage] = useState("");

const onMessageChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
setMessage(e.target.value);
}

const onPressSendMessage = async (e: React.KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) => {
if(e.keyCode == 13 && e.shiftKey == false) {
e.preventDefault();
if(await sendMessage()){
setMessage("");
}
}
}

const sendMessage = async () => {
// Send New Message
if(!props.messageToEdit){
return await sendTextMessage(roomId, message);
}

// Edit Existing Message
const messageToEdit: RealtimeAPIMessage = props.messageToEdit;
props.setMessageToEdit(null);
return await editTextMessage(messageToEdit, message);
}

useEffect(() => {
if(props.messageToEdit){
setMessage(props.messageToEdit.msg);
}
}, [props.messageToEdit]);

return (
<Container>
<TextInput placeholder={"Message"} />
</Container>
<Container>
<TextInput placeholder={"Message"} onChange={onMessageChange} onKeyDown={onPressSendMessage} value={message} />
</Container>
);
}

Expand Down
5 changes: 2 additions & 3 deletions src/components/ChatWindow/MessageList/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,14 @@ const ShowMoreMessages = styled.div`
function MessageList(props : any) {

const messages = props.messages;

return (
<Container>
<ShowMoreMessages onClick={props.loadMoreMessages}>
Show More Messages
</ShowMoreMessages>
{messages ? messages.map((message: RealtimeAPIMessage) => {
{messages ? Object.keys(messages).map((messageId: string) => {
return (
<MessageRow key={message._id} message={message} />
<MessageRow key={messageId} message={messages[messageId]} onEditMessageAction={props.onEditMessageAction} />
);
}) : null}
</Container>
Expand Down
33 changes: 29 additions & 4 deletions src/components/ChatWindow/MessageList/MessageRow/MessageRow.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useState, useEffect, MouseEvent } from "react";
import { hot } from "react-hot-loader/root";
import styled from "styled-components";
import { RealtimeAPIMessage } from '../../../../interfaces/message';
import { getRoomAvatar } from '../../../../util/chatsList.util';
import MessageBodyRender from './components/MessageBodyRender';
import { RealtimeAPIMessage } from "../../../../interfaces/message";
import { getRoomAvatar } from "../../../../util/chatsList.util";
import MessageBodyRender from "./components/MessageBodyRender";
import MessageActions from "./components/MessageActions/MessageActions";
import { deleteMessageById } from "../../../../util/message.util";
const MessageContainer = styled.div`
width:100%;
display: flex;
Expand Down Expand Up @@ -56,16 +58,39 @@ const onChannelMentionClick = (channel: string) => (e: MouseEvent<HTMLDivElement


function MessageRow(props : any) {
const [isShowActionsModal, setActionsModal] = useState(false);
const message: RealtimeAPIMessage = props.message;
const messageDate = new Date(message.ts["$date"]);

const editMessage = () => {
props.onEditMessageAction(message);
}

const deleteMessage = async () => {
await deleteMessageById(message._id);
}

const showActionsModal = () => {
setActionsModal(true);
}

const hideActionsModal = () => {
setActionsModal(false);
}

return (
<MessageContainer>
<MessageContainer onMouseEnter={showActionsModal} onMouseLeave={hideActionsModal}>
<ProfileImage src={getRoomAvatar("/" + message.u.username)}/>
<BodyContainer>
<MessageInfo>
<div style={{color: "#2f343d", fontWeight:"bold", fontSize:"14px"}}>{message.u.name}</div>
<div style={{color: "#9ea2a8", fontSize:"13px", marginLeft:'4px'}}>{message.u.username}</div>
<div style={{color: "#9ea2a8", fontSize:"12px", marginLeft:'4px'}}>{messageDate.getHours() + ":" + messageDate.getMinutes()}</div>
<MessageActions
onMessageDelete = {deleteMessage}
onMessageEdit = {editMessage}
show={isShowActionsModal}
/>
</MessageInfo>
<MessageBody>
<MessageBodyRender
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState } from "react";
import { hot } from "react-hot-loader/root";
import styled from "styled-components";
import { BsThreeDotsVertical } from "react-icons/bs";

const Container = styled.div`
position: absolute;
right: 10px;
cursor: pointer;
`

const ActionsModal = styled.div`
position: fixed;
right: 25px;
background-color: #FFF;
border: 1px solid #eeeff1;
z-index: 100;
`

const ActionsUl = styled.ul`
list-style-type: none;
padding: 5px;
`

const ActionsLi = styled.li`
padding: 2px;
&:hover {
background-color:rgba(186, 186, 186, 0.1);
}
`


function MessageActions(props : any) {
const [isModalOpen, setModal] = useState(false);

const toggleActionsModal = () => {
setModal(!isModalOpen);
}

const onMessageEdit = () => {
props.onMessageEdit();
toggleActionsModal();
}

const onMessageDelete = () => {
props.onMessageDelete();
toggleActionsModal();
}


return props.show && (
<Container>
<div onClick={toggleActionsModal} style={{padding: '3px'}}>
<BsThreeDotsVertical style={{color:'#000'}} />
</div>
{isModalOpen ? (
<ActionsModal>
<ActionsUl style={{listStyleType:"none"}}>
<ActionsLi onClick={onMessageEdit}>Edit Message</ActionsLi>
<ActionsLi onClick={onMessageDelete}>Delete Message</ActionsLi>
</ActionsUl>
</ActionsModal>
): null}
</Container>
);
}

export default hot(MessageActions);
2 changes: 1 addition & 1 deletion src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { app, BrowserWindow, ipcMain } from "electron";
import installExtension, {
REACT_DEVELOPER_TOOLS,
} from "electron-devtools-installer";
import Room from "../interfaces/room";
import { Room } from "../interfaces/room";

declare global {
const MAIN_WINDOW_WEBPACK_ENTRY: string;
Expand Down
26 changes: 26 additions & 0 deletions src/util/message.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { driver } from '@rocket.chat/sdk';
import { RealtimeAPIMessage } from '../interfaces/message';

async function sendTextMessage(roomId: string | undefined, messageText: string) : Promise<boolean> {
if(!roomId) throw new Error('Room ID Not Found!');
let res: RealtimeAPIMessage = await driver.asyncCall("sendMessage", [{
rid: roomId,
msg: messageText
}]);
return res._id ? true : false;
}

async function deleteMessageById(messageId: string) {
await driver.asyncCall("deleteMessage", [{
_id: messageId
}]);
}

async function editTextMessage(message: RealtimeAPIMessage, newMessageText: string) : Promise<boolean> {
let newMessage: RealtimeAPIMessage = message;
newMessage.msg = newMessageText;
await driver.asyncCall("updateMessage", [newMessage]);
return true;
}

export { sendTextMessage, deleteMessageById, editTextMessage };