import { useMutation } from '@tanstack/react-query'; import classNames from 'clsx'; import React, { MutableRefObject, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { uploadMedia } from 'soapbox/actions/media'; import { HStack, IconButton, Stack, Text, Textarea } from 'soapbox/components/ui'; import UploadProgress from 'soapbox/components/upload-progress'; import UploadButton from 'soapbox/features/compose/components/upload_button'; import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; import { truncateFilename } from 'soapbox/utils/media'; import ChatComposer from './chat-composer'; import ChatMessageList from './chat-message-list'; const fileKeyGen = (): number => Math.floor((Math.random() * 0x10000)); interface ChatInterface { chat: IChat, autosize?: boolean, inputRef?: MutableRefObject, className?: string, } /** * Chat UI with just the messages and textarea. * Reused between floating desktop chats and fullscreen/mobile chats. */ const Chat: React.FC = ({ chat, autosize, inputRef, className }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const account = useOwnAccount(); const { createChatMessage, acceptChat } = useChatActions(chat.id); const [content, setContent] = useState(''); const [attachment, setAttachment] = useState(undefined); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [resetFileKey, setResetFileKey] = useState(fileKeyGen()); const [hasErrorSubmittingMessage, setErrorSubmittingMessage] = useState(false); const isSubmitDisabled = content.length === 0 && !attachment; const submitMessage = useMutation(({ chatId, content }: any) => createChatMessage(chatId, content), { retry: false, onMutate: async (newMessage: any) => { // Cancel any outgoing refetches (so they don't overwrite our optimistic update) await queryClient.cancelQueries(['chats', 'messages', chat.id]); // Snapshot the previous value const prevChatMessages = queryClient.getQueryData(['chats', 'messages', chat.id]); const prevContent = content; // Clear state (content, attachment, etc) clearState(); // Optimistically update to the new value queryClient.setQueryData(['chats', 'messages', chat.id], (prevResult: any) => { const newResult = { ...prevResult }; newResult.pages = newResult.pages.map((page: any, idx: number) => { if (idx === 0) { return { ...page, result: [...page.result, { ...newMessage, id: String(Number(new Date())), created_at: new Date(), account_id: account?.id, pending: true, }], }; } return page; }); return newResult; }); // Return a context object with the snapshotted value return { prevChatMessages, prevContent }; }, // If the mutation fails, use the context returned from onMutate to roll back onError: (_error: any, _newData: any, context: any) => { setContent(context.prevContent); queryClient.setQueryData(['chats', 'messages', chat.id], context.prevChatMessages); setErrorSubmittingMessage(true); }, // Always refetch after error or success: onSuccess: () => { queryClient.invalidateQueries(ChatKeys.chatMessages(chat.id)); }, }); const clearState = () => { setContent(''); setAttachment(undefined); setIsUploading(false); setUploadProgress(0); setResetFileKey(fileKeyGen()); setErrorSubmittingMessage(false); }; const sendMessage = () => { if (!isSubmitDisabled && !submitMessage.isLoading) { const params = { content, media_id: attachment && attachment.id, }; submitMessage.mutate({ chatId: chat.id, content }); if (!chat.accepted) { acceptChat.mutate(); } } }; const insertLine = () => setContent(content + '\n'); const handleKeyDown: React.KeyboardEventHandler = (event) => { markRead(); if (event.key === 'Enter' && event.shiftKey) { event.preventDefault(); insertLine(); } else if (event.key === 'Enter') { event.preventDefault(); sendMessage(); } }; const handleContentChange: React.ChangeEventHandler = (event) => { setContent(event.target.value); }; const handlePaste: React.ClipboardEventHandler = (e) => { if (isSubmitDisabled && e.clipboardData && e.clipboardData.files.length === 1) { handleFiles(e.clipboardData.files); } }; const markRead = () => { // markAsRead.mutate(); // dispatch(markChatRead(chatId)); }; const handleMouseOver = () => markRead(); const handleRemoveFile = () => { setAttachment(undefined); setResetFileKey(fileKeyGen()); }; const onUploadProgress = (e: ProgressEvent) => { const { loaded, total } = e; setUploadProgress(loaded / total); }; const handleFiles = (files: FileList) => { setIsUploading(true); const data = new FormData(); data.append('file', files[0]); dispatch(uploadMedia(data, onUploadProgress)).then((response: any) => { setAttachment(response.data); setIsUploading(false); }).catch(() => { setIsUploading(false); }); }; const renderAttachment = () => { if (!attachment) return null; return (
{truncateFilename(attachment.preview_url, 20)}
); }; const renderActionButton = () => { // return canSubmit() ? ( // // ) : ( // // ); }; useEffect(() => { if (inputRef?.current) { inputRef.current.focus(); } }, [chat.id, inputRef?.current]); return (
// {renderAttachment()} // {isUploading && ( // // )} //
//
// {renderActionButton()} //
//