You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
8.4 KiB
JavaScript
191 lines
8.4 KiB
JavaScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import { Input, Flex, Button, Image } from 'antd';
|
|
// import { Input } from 'react-chat-elements';
|
|
import useAuthStore from '@/stores/AuthStore';
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
import {
|
|
SendOutlined,
|
|
MessageOutlined,
|
|
SmileOutlined,
|
|
PictureOutlined,
|
|
CommentOutlined,
|
|
UploadOutlined,
|
|
CloudUploadOutlined,
|
|
FolderAddOutlined,
|
|
FilePdfOutlined,
|
|
CloseCircleOutlined,
|
|
YoutubeOutlined,
|
|
AudioOutlined, PlayCircleOutlined, LoadingOutlined, CheckCircleOutlined, FileOutlined
|
|
} from '@ant-design/icons';
|
|
import { isEmpty, olog } from '@/utils/utils';
|
|
import { v4 as uuid } from 'uuid';
|
|
import { sentMsgTypeMapped } from '@/lib/msgUtils';
|
|
import InputTemplate from './Input/Template';
|
|
import InputEmoji from './Input/Emoji';
|
|
import InputMediaUpload from './Input/MediaUpload';
|
|
import dayjs from 'dayjs';
|
|
|
|
const InputComposer = () => {
|
|
const userId = useAuthStore(state => state.loginUser.userId);
|
|
const websocket = useConversationStore(state => state.websocket);
|
|
const websocketOpened = useConversationStore(state => state.websocketOpened);
|
|
const currentConversation = useConversationStore(state => state.currentConversation);
|
|
const referenceMsg = useConversationStore(state => state.referenceMsg);
|
|
const setReferenceMsg = useConversationStore(state => state.setReferenceMsg);
|
|
const complexMsg = useConversationStore(state => state.complexMsg);
|
|
const setComplexMsg = useConversationStore(state => state.setComplexMsg);
|
|
const sentOrReceivedNewMessage = useConversationStore(state => state.sentOrReceivedNewMessage);
|
|
|
|
const talkabled = !isEmpty(currentConversation.sn) && websocketOpened;
|
|
const gt24h = currentConversation.last_received_time ? dayjs().diff(dayjs(currentConversation.last_received_time), 'hour') > 24 : true;
|
|
const textabled = talkabled && !gt24h;
|
|
|
|
const textInputRef = useRef(null);
|
|
const [textContent, setTextContent] = useState('');
|
|
|
|
const invokeSendMessage = (msgObj) => {
|
|
const msgObjMerge = {
|
|
sender: 'me',
|
|
senderName: 'me',
|
|
to: currentConversation.whatsapp_phone_number,
|
|
date: new Date(),
|
|
status: 'waiting',
|
|
...(referenceMsg.id ? { context: { message_id: referenceMsg.id }, message_origin: referenceMsg } : {}),
|
|
...msgObj,
|
|
id: `${currentConversation.sn}.${uuid()}`,
|
|
};
|
|
olog('sendMessage------------------', msgObjMerge)
|
|
const contentToSend = sentMsgTypeMapped[msgObjMerge.type].contentToSend(msgObjMerge);
|
|
console.log('content to send-------------------------------------', contentToSend);
|
|
websocket.sendMessage({ ...contentToSend, opi_sn: userId, coli_sn: currentConversation.coli_sn });
|
|
const contentToRender = sentMsgTypeMapped[msgObjMerge.type].contentToRender(msgObjMerge);
|
|
console.log(contentToRender, 'contentToRender sendMessage------------------');
|
|
sentOrReceivedNewMessage(contentToRender.conversationid, contentToRender);
|
|
|
|
setTextContent('');
|
|
setReferenceMsg({});
|
|
setComplexMsg({});
|
|
};
|
|
|
|
/**
|
|
* 先推到消息记录上面, 再发送
|
|
*/
|
|
const invokeUploadFileMessage = (msgObj) => {
|
|
const msgObjMerge = {
|
|
sender: 'me',
|
|
senderName: 'me',
|
|
to: currentConversation.whatsapp_phone_number,
|
|
date: new Date(),
|
|
status: 'waiting',
|
|
...(referenceMsg.id ? { context: { message_id: referenceMsg.id }, message_origin: referenceMsg } : {}),
|
|
...msgObj,
|
|
id: `${currentConversation.sn}.${msgObj.id}`,
|
|
};
|
|
const contentToRender = sentMsgTypeMapped[msgObjMerge.type].contentToRender(msgObjMerge);
|
|
sentOrReceivedNewMessage(contentToRender.conversationid, contentToRender);
|
|
};
|
|
|
|
const invokeSendUploadMessage = (msgObj) => {
|
|
const msgObjMerge = {
|
|
sender: 'me',
|
|
senderName: 'me',
|
|
to: currentConversation.whatsapp_phone_number,
|
|
date: new Date(),
|
|
status: 'waiting',
|
|
...(referenceMsg.id ? { context: { message_id: referenceMsg.id }, message_origin: referenceMsg } : {}),
|
|
...msgObj,
|
|
id: `${currentConversation.sn}.${msgObj.id}`,
|
|
};
|
|
const contentToSend = sentMsgTypeMapped[msgObjMerge.type].contentToSend(msgObjMerge);
|
|
websocket.sendMessage({ ...contentToSend, opi_sn: userId, coli_sn: currentConversation.coli_sn });
|
|
}
|
|
|
|
const focusInput = () => {
|
|
textInputRef.current.focus({ cursor: 'end', preventScroll: true, });
|
|
};
|
|
|
|
const addEmoji = emoji => {
|
|
setTextContent(prevValue => {
|
|
return prevValue + emoji;
|
|
});
|
|
};
|
|
|
|
const handleSendText = () => {
|
|
if (textContent.trim() !== '' || !isEmpty(complexMsg)) {
|
|
const msgObj = {
|
|
type: 'text',
|
|
text: textContent,
|
|
...complexMsg,
|
|
};
|
|
invokeSendMessage(msgObj);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
focusInput();
|
|
return () => {};
|
|
}, [referenceMsg, complexMsg]);
|
|
|
|
return (
|
|
<div>
|
|
{referenceMsg.id && (
|
|
<Flex justify='space-between' className='reply-to bg-gray-100 p-1 rounded-none text-slate-500'>
|
|
<div className='referrer-msg border-l-3 border-y-0 border-r-0 border-slate-300 border-solid pl-2 pr-1 py-1'>
|
|
<span className=' text-primary pr-1 italic align-top'>{referenceMsg.senderName}</span>
|
|
{referenceMsg.type === 'photo' && <Image width={100} src={referenceMsg.data.uri} />}
|
|
<span className='px-1'>{referenceMsg.originText}</span>
|
|
</div>
|
|
<Button type='text' title='取消引用' className=' rounded-none text-slate-500' icon={<CloseCircleOutlined />} size={'middle'} onClick={() => setReferenceMsg({})} />
|
|
</Flex>
|
|
)}
|
|
{complexMsg.id && (
|
|
<Flex justify='space-between' className='reply-to bg-gray-100 p-1 rounded-none text-slate-500'>
|
|
<div className='pl-2 pr-1 py-1'>
|
|
{(complexMsg.type === 'photo' && complexMsg.data.uri) && <Image width={100} src={complexMsg.data.uri} />}
|
|
{complexMsg.type === 'video' && <FileOutlined className=' text-red-400' />}
|
|
{complexMsg.type !== 'photo' && <span className='px-1'>{complexMsg.name}</span>}
|
|
{complexMsg.status === 'loading' && <LoadingOutlined className='px-1' />}
|
|
{complexMsg.status === 'done' && <CheckCircleOutlined className='px-1 text-primary' />}
|
|
{complexMsg.status === 'error' && <><CloseCircleOutlined className='px-1 text-red-400' /> <span>添加失败</span> </>}
|
|
</div>
|
|
<Button type='text' title='删除' className=' rounded-none text-slate-500' icon={<CloseCircleOutlined />} size={'middle'} onClick={() => setComplexMsg({})} />
|
|
</Flex>
|
|
)}
|
|
<Input.TextArea
|
|
ref={textInputRef}
|
|
size='large'
|
|
placeholder={gt24h ? 'This session has expired. Please send a template message to activate the session' : 'Enter for Send, Shift+Enter for new line'}
|
|
rows={2}
|
|
disabled={!textabled}
|
|
value={textContent}
|
|
onChange={(e) => setTextContent(e.target.value)}
|
|
className='rounded-b-none emoji'
|
|
onPressEnter={(e) => {
|
|
if (!e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSendText();
|
|
}
|
|
}}
|
|
autoSize={{ minRows: 2, maxRows: 6 }}
|
|
/>
|
|
<Flex justify={'space-between'} className=' bg-gray-200 p-1 rounded-b'>
|
|
<Flex gap={4} className='*:text-primary *:rounded-none'>
|
|
<InputTemplate key='templates' disabled={!talkabled || textabled} invokeSendMessage={invokeSendMessage} />
|
|
<InputEmoji key='emoji' disabled={!textabled} inputEmoji={addEmoji} />
|
|
<InputMediaUpload key={'addNewMedia'} disabled={!textabled} {...{invokeUploadFileMessage, invokeSendUploadMessage}} />
|
|
{/* <Button type='text' className='' icon={<YoutubeOutlined />} size={'middle'} />
|
|
<Button type='text' className='' icon={<AudioOutlined />} size={'middle'} />
|
|
<Button type='text' className='' icon={<FolderAddOutlined />} size={'middle'} />
|
|
<Button type='text' className='' icon={<CloudUploadOutlined />} size={'middle'} />
|
|
<Button type='text' className='' icon={<FilePdfOutlined />} size={'middle'} /> */}
|
|
</Flex>
|
|
<Button key={'send-btn'} onClick={handleSendText} type='primary' size='middle' icon={<SendOutlined />} disabled={!textabled}>
|
|
Send
|
|
</Button>
|
|
</Flex>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default InputComposer;
|