Skip to content

Commit

Permalink
Game:Add support for replying to messages in Message box
Browse files Browse the repository at this point in the history
This commit lets you reply to others in chat during game.

fixes #153
  • Loading branch information
ritwik-69 committed Jun 25, 2024
1 parent 5899667 commit 7fb2f1a
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 33 deletions.
16 changes: 15 additions & 1 deletion backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export enum ChatEventTypes {
REACT_TO_MESSAGE = 'REACT_TO_MESSAGE',
DELETE_MESSAGE = 'DELETE_MESSAGE',
EDIT_MESSAGE = 'EDIT_MESSAGE',
REPLY_MESSAGE = 'REPLY_MESSAGE',
}

export type ChatEvent =
Expand All @@ -156,6 +157,14 @@ export type ChatEvent =
ref: string;
newContent: string;
};
}
| {
type: ChatEventTypes.REPLY_MESSAGE;
data: {
ref: string;
name: string;
data: string;
};
};

export type AppEventType = GameEventTypes | ChatEventTypes;
Expand All @@ -167,12 +176,17 @@ export type AppEvent = GameEvent | ChatEvent;
export type ChatMessage = {
content: string;
id: string;
ref?: string | null;
ref?: replyMessage | null;
atMentions?: string[];
reactions?: [string, string][];
playerName: string;
};

export type replyMessage = {
ref?: string;
data?: string;
};

export type ClientId = string;

//todo: Add more events
Expand Down
27 changes: 25 additions & 2 deletions frontend/src/library/chatbox/Chatbox.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { useEffect, useState } from 'react';
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import { ChatEventTypes, ChatMessage } from '../../../../backend/src/types';
import {
ChatEventTypes,
ChatMessage,
replyMessage,
} from '../../../../backend/src/types';
import { FaComments } from 'react-icons/fa';
import * as channel from '../../channel';
import { useToast } from '../toast/toast-context';
Expand All @@ -12,6 +16,7 @@ import { useToast } from '../toast/toast-context';
const Chatbox: React.FC = () => {
const [messages, setMessages] = useState<{ [k: string]: ChatMessage }>({});
const [isVisible, setIsVisible] = useState(false);
const [replyMessage, setReplyMessage] = useState<replyMessage | null>(null);
const toast = useToast();

useEffect(() => {
Expand Down Expand Up @@ -58,6 +63,24 @@ const Chatbox: React.FC = () => {
};
});
break;
case ChatEventTypes.REPLY_MESSAGE:
if (event.data) {
const message = messages[event.data.ref];
if (message) {
setReplyMessage({
ref: message.playerName,
data: message.content,
});
} else {
console.log(
'Message not found for reply:',
event.data.ref
);
}
} else {
setReplyMessage(null);
}
break;
}
});
}, [messages, isVisible, toast]);
Expand All @@ -81,7 +104,7 @@ const Chatbox: React.FC = () => {
<MessageList messages={Object.values(messages)} />
</div>
<div className="border-t border-gray-300">
<MessageInput />
<MessageInput replyMessage={replyMessage} />
</div>
</div>
</div>
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/library/chatbox/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,39 @@ const Message: React.FC<MessageProps> = ({ message }) => {
});
setShowEmojiPicker(false);
};
const handleReply = () => {
triggerEvent({
type: ChatEventTypes.REPLY_MESSAGE,
data: {
ref: message.id,
name: message.playerName,
data: message.content,
},
});
};

return (
<div className="relative bg-gray-200 text-gray-800 p-2.5 mb-6 max-w-xs rounded-xl">
<div className="absolute -top-2 left-0 text-blue-900 font-kavoon text-xs px-2 py-0 rounded-t-2xl bg-gray-200">
{message.playerName}
</div>

{message.ref && message.ref.data && (
<div className="flex-1 p-2 bg-gray-300 rounded-t-lg border border-gray-500 ">
<p className="text-sm text-gray-900 font-kavoon">
{message.ref.ref}
</p>

<p className="text-sm text-gray-600 font-kavoon">
{message.ref &&
message.ref.data &&
message.ref.data.length > 35
? message.ref.data.slice(0, 35) + '...'
: message.ref.data}
</p>
</div>
)}

<div className="break-words font-kavoon">{message.content}</div>
<div className="absolute bottom-0 left-3 transform translate-x-1/4 translate-y-1/2 w-4 h-4 bg-gray-200 rotate-45"></div>
<div className="mt-1 flex space-x-1 items-center relative">
Expand All @@ -46,6 +73,12 @@ const Message: React.FC<MessageProps> = ({ message }) => {
<EmojiPicker onEmojiClick={handleEmojiClick} />
</div>
)}
<button
className="text-gray-500 hover:text-gray-700"
onClick={handleReply}
>
Reply
</button>
</div>
</div>
);
Expand Down
97 changes: 67 additions & 30 deletions frontend/src/library/chatbox/MessageInput.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import React, { useState } from 'react';
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react';
import Button from '../button';
import { ChatEventTypes } from '../../../../backend/src/types';
import { ChatEventTypes, replyMessage } from '../../../../backend/src/types';
import { triggerEvent } from '../../channel';
import { useAuth } from '../../contexts/AuthContext';

const MessageInput: React.FC = () => {
interface MessageInputProps {
replyMessage?: replyMessage | null; // Optional prop
}

const MessageInput: React.FC<MessageInputProps> = ({ replyMessage = null }) => {
const [content, setContent] = useState('');
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const auth = useAuth();

const handleSend = async () => {
handleClose();
if (content.trim() !== '') {
triggerEvent({
type: ChatEventTypes.SEND_MESSAGE,
data: {
id: Date.now().toString(),
content: content.trim(),
playerName: auth.getUser()?.name || 'Unknown user',
ref: replyMessage,
},
});
setContent('');
Expand All @@ -34,38 +40,69 @@ const MessageInput: React.FC = () => {
handleSend();
}
};
const handleClose = () => {
triggerEvent({
type: ChatEventTypes.REPLY_MESSAGE,
data: null,
});
};

return (
<div className="relative flex p-1 border-t border-gray-300">
<button
className="mr-1 p-2 bg-white text-gray-800 rounded-xl border-2 border-gray-500"
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
>
😊
</button>
{showEmojiPicker && (
<div className="absolute bottom-14 left-0 z-50">
<EmojiPicker onEmojiClick={onEmojiClick} />
<div className="relative p-1 border-t border-gray-300">
{replyMessage && (
<div className="flex items-center mb-2">
<div className="flex-1 p-2 bg-gray-200 rounded-t-lg border-b border-gray-500 ">
<p className="text-sm text-gray-900 font-kavoon">
Replying to: {replyMessage.ref}
</p>

<p className="text-sm text-gray-600 font-kavoon">
{replyMessage &&
replyMessage.data &&
replyMessage.data.length > 35
? replyMessage.data.slice(0, 35) + '...'
: replyMessage.data}
</p>
<button
className="absolute right-0 top-0 px-2 py-0 bg-none cursor-pointer border-transparent text-black"
onClick={handleClose}
>
{'x'}
</button>
</div>
</div>
)}
<input
type="text"
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleKeyDown}
className="flex-0.5 px-1 py-1 border-2 border-gray-500 rounded-xl font-kavoon text-sm"
placeholder="Type a message..."
/>
<Button
variant="accept"
size="medium"
backgroundColor="bg-gray-400"
buttonSize="w-34 h-11"
className="ml-2 border-2"
onClick={handleSend}
>
Send
</Button>
<div className="flex items-center">
<button
className="mr-1 p-2 bg-white text-gray-800 rounded-xl border-2 border-gray-500"
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
>
😊
</button>
{showEmojiPicker && (
<div className="absolute bottom-14 left-0 z-50">
<EmojiPicker onEmojiClick={onEmojiClick} />
</div>
)}
<input
type="text"
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleKeyDown}
className="flex-1 px-1 py-1 border-2 border-gray-500 rounded-xl font-kavoon text-sm"
placeholder="Type a message..."
/>
<Button
variant="accept"
size="medium"
backgroundColor="bg-gray-400"
buttonSize="w-34 h-11"
className="ml-2 border-2"
onClick={handleSend}
>
Send
</Button>
</div>
</div>
);
};
Expand Down

0 comments on commit 7fb2f1a

Please sign in to comment.