feature/315_chat_typing_status do development 3 lat temu
| @@ -88,5 +88,19 @@ namespace Diligent.WebAPI.Host.Hubs | |||
| await _mediator.Send(new RemoveUserFromGroupCommand(room.RoomId, room.UserId)); | |||
| } | |||
| } | |||
| [HubMethodName("SeeWhoIsTyping")] | |||
| public async Task SeeWhoIsTyping(TypingInvokeMessage message) | |||
| { | |||
| // Find user's room and send message to everyone | |||
| //if (_connections.TryGetValue(Context.ConnectionId, out UserConnection room)) | |||
| if (_connections.TryGetValue(message.ConnId, out UserConnection room)) | |||
| { | |||
| // send the typing info to all in group except the caller | |||
| // viewtyping(username + "is typing") // message, roomId | |||
| //await Clients.GroupExcept(message.RoomId, Context.ConnectionId).ViewTyping(new TypingMessage { RoomId = room.RoomId, Message = message.Message }); | |||
| await Clients.GroupExcept(message.RoomId, message.ConnId).ViewTyping(new TypingMessage { RoomId = room.RoomId, Message = message.Message }); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -5,5 +5,6 @@ | |||
| Task ReceiveMessage(ChatMessage message); | |||
| Task ReceiveNotifications(string userId, string roomId); | |||
| Task ViewTyping(TypingMessage message); | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| namespace Diligent.WebAPI.Host.Hubs | |||
| { | |||
| public class TypingInvokeMessage | |||
| { | |||
| public string ConnId { get; set; } | |||
| public string RoomId { get; set; } | |||
| public string Message { get; set; } | |||
| } | |||
| public class TypingMessage | |||
| { | |||
| public string Message { get; set; } | |||
| public string RoomId { get; set; } | |||
| } | |||
| } | |||
| @@ -1,13 +1,13 @@ | |||
| namespace Diligent.WebAPI.Host.Hubs | |||
| { | |||
| public class UserConnection | |||
| public class UserStatus | |||
| { | |||
| // UserId in hub | |||
| public string UserId { get; set; } | |||
| // Username in hub | |||
| public string Mode { get; set; } | |||
| } | |||
| public class UserConnection : UserStatus | |||
| { | |||
| public string Username { get; set; } | |||
| // RoomId in hub | |||
| public string RoomId { get; set; } | |||
| } | |||
| @@ -100,6 +100,11 @@ const ChatList = () => { | |||
| }) | |||
| ); | |||
| }); | |||
| connection.on("ViewTyping", (data) =>{ | |||
| dispatch(chatActions.addTyping(data)); | |||
| }) | |||
| connection.on("ReceiveNotifications", (userId, roomId) => { | |||
| if (user.id !== userId) dispatch(chatActions.addNotification(roomId)); | |||
| }); | |||
| @@ -4,6 +4,7 @@ import { UserContext } from "../contexts/userContext"; | |||
| import { useSelector, useDispatch } from "react-redux"; | |||
| import { chatActions } from "../store/chat-slice"; | |||
| import { BsCircleFill } from 'react-icons/bs' | |||
| import TypingBar from "./TypingBar"; | |||
| const ChatWindow = ({ room }) => { | |||
| const messagesEndRef = useRef(null); | |||
| @@ -15,6 +16,7 @@ const ChatWindow = ({ room }) => { | |||
| const [message, setMessage] = useState(""); | |||
| const { user } = useContext(UserContext); | |||
| const connection = useSelector((state) => state.chat.connection); | |||
| const { typings } = useSelector((s) => s.chat) | |||
| const connections = useSelector((state) => state.chat.connections); | |||
| const activeRoom = useSelector((state) => state.chat.activeRoom); | |||
| const messages = useSelector((state) => state.chat.messages); | |||
| @@ -60,6 +62,24 @@ const ChatWindow = ({ room }) => { | |||
| const status = useSelector((s) => s.status); | |||
| const { activeUsers } = status; | |||
| const messageOnChangeHandler = async (e) =>{ | |||
| setMessage(e.target.value) | |||
| const userToFetch = connections.filter( | |||
| (conn) => conn.userId === user.id && conn.roomId === activeRoom.id | |||
| ); | |||
| // console.log(userToFetch[0].connId) | |||
| // console.log(room.id) | |||
| await connection.send("SeeWhoIsTyping", { | |||
| message: `${user.username} is typing...`, | |||
| connId: userToFetch[0].connId, | |||
| roomId: activeRoom.id | |||
| }); | |||
| } | |||
| return ( | |||
| <div className="px-3 bg-light-transparent rounded h-100 d-flex flex-column"> | |||
| <div style={{ height: "80px" }}> | |||
| @@ -119,13 +139,15 @@ const ChatWindow = ({ room }) => { | |||
| <div ref={messagesEndRef} /> | |||
| </div> | |||
| <TypingBar id={activeRoom.id}/> | |||
| <Form style={{ height: "80px" }} className="d-flex align-items-center"> | |||
| <InputGroup className="mb-3"> | |||
| <FormControl | |||
| placeholder="Enter your messagge..." | |||
| aria-label="Enter your messagge..." | |||
| aria-describedby="basic-addon2" | |||
| onChange={(e) => setMessage(e.target.value)} | |||
| onChange={(e) => messageOnChangeHandler(e)} | |||
| /> | |||
| <Button | |||
| @@ -0,0 +1,28 @@ | |||
| import React, { useEffect, useState } from 'react' | |||
| import { useDispatch, useSelector } from 'react-redux' | |||
| import { chatActions } from '../store/chat-slice'; | |||
| const TypingBar = ({id}) => { | |||
| const [rerender, setRerender] = useState(false) | |||
| const dispatch = useDispatch(); | |||
| const {typings} = useSelector(s => s.chat) | |||
| // On componentDidMount set the timer | |||
| useEffect(() => { | |||
| const timeout = setTimeout(() => { | |||
| dispatch(chatActions.removeTyping()); | |||
| setRerender(!rerender) | |||
| return () => clearTimeout(timeout) | |||
| }, 2500) | |||
| }, [rerender]); | |||
| return ( | |||
| <div className='text-dark'> | |||
| {typings.filter(n => n.roomId === id).length > 1 ? <p className='p-0 m-0 text-start w-100 text-muted pt-1'>2 or more users are typing</p> : typings.map(n=> n.roomId === id ? <p key={n.message} className='p-0 m-0 text-start w-100 text-muted pt-1'>{n.message}</p> : '')} | |||
| </div> | |||
| ) | |||
| } | |||
| export default TypingBar | |||
| @@ -15,6 +15,7 @@ const initialState = { | |||
| connections: [], | |||
| // Notifications | |||
| notifications: [], | |||
| typings: [] | |||
| }; | |||
| export const fetchChatRoomsAsync = createAsyncThunk( | |||
| @@ -144,6 +145,17 @@ const chatSlice = createSlice({ | |||
| (customer) => customer.customerId !== action.payload.customerId | |||
| ); | |||
| }, | |||
| addTyping: (state, action) => { | |||
| if(!state.typings.some(n => n.message === action.payload.message && n.roomId === action.payload.roomId)) | |||
| state.typings = [...state.typings, action.payload]; | |||
| else console.log('ima vec') | |||
| }, | |||
| removeTyping: (state, action) => { | |||
| if(state.typings.length >= 1){ | |||
| let f = state.typings[0] | |||
| state.typings = state.typings.filter(n => n.message !== f.message); | |||
| } | |||
| } | |||
| }, | |||
| extraReducers: (builder) => { | |||
| // Fetch chat rooms | |||