diff --git a/packages/react/src/lib/createPendingMessage.js b/packages/react/src/lib/createPendingMessage.js index da8bdc3044..72cd04e9e5 100644 --- a/packages/react/src/lib/createPendingMessage.js +++ b/packages/react/src/lib/createPendingMessage.js @@ -53,4 +53,84 @@ const createPendingMessage = ( }; }; -export default createPendingMessage; +const createPendingAttachmentMessage = ( + file, + user = { _id: undefined, username: undefined, name: undefined }, + description = '', + fileType = 'file', + additionalFields = {} +) => { + const now = new Date(); + + const fileProps = { + _id: file._id || '', + name: file.name, + type: file.type, + size: file.size, + format: file.format || '', + }; + + const attachment = { + ts: '1970-01-01T00:00:00.000Z', + title: file.name, + title_link: `/file-upload/${file._id || file.name}/${encodeURIComponent( + file.name + )}`, + title_link_download: true, + type: 'file', + description, + descriptionMd: [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'PLAIN_TEXT', + value: description, + }, + ], + }, + ], + }; + + if (fileType === 'audio') { + attachment.audio_url = `/file-upload/${ + file._id || file.name + }/${encodeURIComponent(file.name)}`; + attachment.audio_type = file.type; + attachment.audio_size = file.size; + } else if (fileType === 'video') { + attachment.video_url = `/file-upload/${ + file._id || file.name + }/${encodeURIComponent(file.name)}`; + attachment.video_type = file.type; + attachment.video_size = file.size; + } else if (fileType === 'image') { + attachment.image_preview = file.image_preview || ''; + attachment.image_url = file.image_url; + attachment.image_type = file.type; + attachment.image_size = file.size; + } + + return { + isPending: true, + _id: `ec_${createRandomId()}`, + rid: 'GENERAL', + msg: '', + ts: now.toISOString(), + u: { + _id: user._id, + username: user.username, + name: user.name, + }, + _updatedAt: now.toISOString(), + urls: [], + mentions: [], + channels: [], + file: fileProps, + files: [fileProps], + attachments: [attachment], + md: [], + }; +}; + +export { createPendingMessage, createPendingAttachmentMessage }; diff --git a/packages/react/src/views/AttachmentPreview/AttachmentPreview.js b/packages/react/src/views/AttachmentPreview/AttachmentPreview.js index 2c007f1a71..c0f5b0d494 100644 --- a/packages/react/src/views/AttachmentPreview/AttachmentPreview.js +++ b/packages/react/src/views/AttachmentPreview/AttachmentPreview.js @@ -4,7 +4,8 @@ import { Box, Icon, Button, Input, Modal } from '@embeddedchat/ui-elements'; import useAttachmentWindowStore from '../../store/attachmentwindow'; import CheckPreviewType from './CheckPreviewType'; import RCContext from '../../context/RCInstance'; -import { useMessageStore, useMemberStore } from '../../store'; +import { useMessageStore, useMemberStore, useUserStore } from '../../store'; +import { createPendingAttachmentMessage } from '../../lib/createPendingMessage'; import getAttachmentPreviewStyles from './AttachmentPreview.styles'; import { parseEmoji } from '../../lib/emoji'; import MembersList from '../Mentions/MembersList'; @@ -51,15 +52,43 @@ const AttachmentPreview = () => { searchMentionUser(description); }; + const upsertMessage = useMessageStore((state) => state.upsertMessage); + const removeMessage = useMessageStore((state) => state.removeMessage); + const { username, userId, name } = useUserStore((state) => ({ + username: state.username, + userId: state.userId, + name: state.name, + })); + const userInfo = { _id: userId, username, name }; + const submit = async () => { setIsPending(true); - await RCInstance.sendAttachment( + + const type = data ? data.type.split('/')[0] : ''; + const pendingFileMessage = createPendingAttachmentMessage( + data, + userInfo, + messageRef.current.value, + type + ); + + upsertMessage(pendingFileMessage, ECOptions.enableThreads); + if (isPending) { + setIsPending(false); + } + toggle(); + + const res = await RCInstance.sendAttachment( data, fileName, messageRef.current.value, ECOptions?.enableThreads ? threadId : undefined ); - toggle(); + + if (res.success) { + removeMessage(pendingFileMessage._id); + } + setData(null); if (isPending) { setIsPending(false); diff --git a/packages/react/src/views/ChatInput/ChatInput.js b/packages/react/src/views/ChatInput/ChatInput.js index 34ebffb86f..6527bc4eb1 100644 --- a/packages/react/src/views/ChatInput/ChatInput.js +++ b/packages/react/src/views/ChatInput/ChatInput.js @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useContext } from 'react'; import { css } from '@emotion/react'; import { Box, @@ -24,7 +24,7 @@ import ChatInputFormattingToolbar from './ChatInputFormattingToolbar'; import useAttachmentWindowStore from '../../store/attachmentwindow'; import MembersList from '../Mentions/MembersList'; import { TypingUsers } from '../TypingUsers'; -import createPendingMessage from '../../lib/createPendingMessage'; +import { createPendingMessage } from '../../lib/createPendingMessage'; import { CommandsList } from '../CommandList'; import useSettingsStore from '../../store/settingsStore'; import ChannelState from '../ChannelState/ChannelState'; @@ -57,6 +57,7 @@ const ChatInput = ({ scrollToBottom }) => { const [showCommandList, setShowCommandList] = useState(false); const [filteredCommands, setFilteredCommands] = useState([]); const [isMsgLong, setIsMsgLong] = useState(false); + const [messageQueue, setMessageQueue] = useState([]); const { isUserAuthenticated, @@ -164,6 +165,43 @@ const ChatInput = ({ scrollToBottom }) => { } }, [editMessage]); + useEffect(() => { + const handleOnline = async () => { + if (navigator.onLine && messageQueue.length > 0) { + for (let i = 0; i < messageQueue.length; i += 1) { + const pendingMessage = JSON.parse(messageQueue[i]); + // eslint-disable-next-line no-await-in-loop + const res = await RCInstance.sendMessage( + { + msg: pendingMessage.msg, + _id: pendingMessage._id, + }, + ECOptions.enableThreads ? threadId : undefined + ); + if (res.success) { + clearQuoteMessages(); + replaceMessage(pendingMessage, res.message); + } + } + setMessageQueue([]); + localStorage.removeItem('pendingMessage'); + } + }; + + window.addEventListener('online', handleOnline); + return () => { + window.removeEventListener('online', handleOnline); + }; + }, [messageQueue]); + + useEffect(() => { + const pendingMessage = localStorage.getItem('pendingMessage'); + if (pendingMessage) { + messageRef.current.value = JSON.parse(pendingMessage).msg || ''; + localStorage.removeItem('pendingMessage'); + } + }, []); + const getMessageLink = async (id) => { const host = RCInstance.getHost(); const res = await RCInstance.channelInfo(); @@ -262,6 +300,16 @@ const ChatInput = ({ scrollToBottom }) => { } }; + const handleOffline = (pendingMessage) => { + localStorage.removeItem('pendingMessage'); + localStorage.setItem('pendingMessage', JSON.stringify(pendingMessage)); + + setMessageQueue((prevQueue) => [ + ...prevQueue, + JSON.stringify(pendingMessage), + ]); + }; + const handleSendNewMessage = async (message) => { messageRef.current.value = ''; setDisableButton(true); @@ -303,6 +351,11 @@ const ChatInput = ({ scrollToBottom }) => { upsertMessage(pendingMessage, ECOptions.enableThreads); + if (!navigator.onLine) { + handleOffline(pendingMessage); + return; + } + const res = await RCInstance.sendMessage( { msg: pendingMessage.msg,