feature/315_chat_typing_status в development 3 лет назад
| await _mediator.Send(new RemoveUserFromGroupCommand(room.RoomId, room.UserId)); | 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 }); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } |
| Task ReceiveMessage(ChatMessage message); | Task ReceiveMessage(ChatMessage message); | ||||
| Task ReceiveNotifications(string userId, string roomId); | Task ReceiveNotifications(string userId, string roomId); | ||||
| Task ViewTyping(TypingMessage message); | |||||
| } | } | ||||
| } | } |
| 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; } | |||||
| } | |||||
| } |
| namespace Diligent.WebAPI.Host.Hubs | namespace Diligent.WebAPI.Host.Hubs | ||||
| { | { | ||||
| public class UserConnection | |||||
| public class UserStatus | |||||
| { | { | ||||
| // UserId in hub | |||||
| public string UserId { get; set; } | public string UserId { get; set; } | ||||
| // Username in hub | |||||
| public string Mode { get; set; } | |||||
| } | |||||
| public class UserConnection : UserStatus | |||||
| { | |||||
| public string Username { get; set; } | public string Username { get; set; } | ||||
| // RoomId in hub | // RoomId in hub | ||||
| public string RoomId { get; set; } | public string RoomId { get; set; } | ||||
| } | } |
| }) | }) | ||||
| ); | ); | ||||
| }); | }); | ||||
| connection.on("ViewTyping", (data) =>{ | |||||
| dispatch(chatActions.addTyping(data)); | |||||
| }) | |||||
| connection.on("ReceiveNotifications", (userId, roomId) => { | connection.on("ReceiveNotifications", (userId, roomId) => { | ||||
| if (user.id !== userId) dispatch(chatActions.addNotification(roomId)); | if (user.id !== userId) dispatch(chatActions.addNotification(roomId)); | ||||
| }); | }); |
| import { useSelector, useDispatch } from "react-redux"; | import { useSelector, useDispatch } from "react-redux"; | ||||
| import { chatActions } from "../store/chat-slice"; | import { chatActions } from "../store/chat-slice"; | ||||
| import { BsCircleFill } from 'react-icons/bs' | import { BsCircleFill } from 'react-icons/bs' | ||||
| import TypingBar from "./TypingBar"; | |||||
| const ChatWindow = ({ room }) => { | const ChatWindow = ({ room }) => { | ||||
| const messagesEndRef = useRef(null); | const messagesEndRef = useRef(null); | ||||
| const [message, setMessage] = useState(""); | const [message, setMessage] = useState(""); | ||||
| const { user } = useContext(UserContext); | const { user } = useContext(UserContext); | ||||
| const connection = useSelector((state) => state.chat.connection); | const connection = useSelector((state) => state.chat.connection); | ||||
| const { typings } = useSelector((s) => s.chat) | |||||
| const connections = useSelector((state) => state.chat.connections); | const connections = useSelector((state) => state.chat.connections); | ||||
| const activeRoom = useSelector((state) => state.chat.activeRoom); | const activeRoom = useSelector((state) => state.chat.activeRoom); | ||||
| const messages = useSelector((state) => state.chat.messages); | const messages = useSelector((state) => state.chat.messages); | ||||
| const status = useSelector((s) => s.status); | const status = useSelector((s) => s.status); | ||||
| const { activeUsers } = 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 ( | return ( | ||||
| <div className="px-3 bg-light-transparent rounded h-100 d-flex flex-column"> | <div className="px-3 bg-light-transparent rounded h-100 d-flex flex-column"> | ||||
| <div style={{ height: "80px" }}> | <div style={{ height: "80px" }}> | ||||
| <div ref={messagesEndRef} /> | <div ref={messagesEndRef} /> | ||||
| </div> | </div> | ||||
| <TypingBar id={activeRoom.id}/> | |||||
| <Form style={{ height: "80px" }} className="d-flex align-items-center"> | <Form style={{ height: "80px" }} className="d-flex align-items-center"> | ||||
| <InputGroup className="mb-3"> | <InputGroup className="mb-3"> | ||||
| <FormControl | <FormControl | ||||
| placeholder="Enter your messagge..." | placeholder="Enter your messagge..." | ||||
| aria-label="Enter your messagge..." | aria-label="Enter your messagge..." | ||||
| aria-describedby="basic-addon2" | aria-describedby="basic-addon2" | ||||
| onChange={(e) => setMessage(e.target.value)} | |||||
| onChange={(e) => messageOnChangeHandler(e)} | |||||
| /> | /> | ||||
| <Button | <Button |
| 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 |
| connections: [], | connections: [], | ||||
| // Notifications | // Notifications | ||||
| notifications: [], | notifications: [], | ||||
| typings: [] | |||||
| }; | }; | ||||
| export const fetchChatRoomsAsync = createAsyncThunk( | export const fetchChatRoomsAsync = createAsyncThunk( | ||||
| (customer) => customer.customerId !== action.payload.customerId | (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) => { | extraReducers: (builder) => { | ||||
| // Fetch chat rooms | // Fetch chat rooms |