Error handling for bad sends

chats-router
Justin 2022-09-08 10:24:20 -04:00
rodzic 6b38e37019
commit c48b4adc81
4 zmienionych plików z 33 dodań i 39 usunięć

Wyświetl plik

@ -1,5 +1,5 @@
import { Map as ImmutableMap } from 'immutable'; import { Map as ImmutableMap } from 'immutable';
import React, { useEffect, useRef } from 'react'; import React, { useEffect } from 'react';
import { fetchChat, markChatRead } from 'soapbox/actions/chats'; import { fetchChat, markChatRead } from 'soapbox/actions/chats';
import { Column } from 'soapbox/components/ui'; import { Column } from 'soapbox/components/ui';
@ -22,22 +22,12 @@ interface IChatRoom {
const ChatRoom: React.FC<IChatRoom> = ({ params }) => { const ChatRoom: React.FC<IChatRoom> = ({ params }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const displayFqn = useAppSelector(getDisplayFqn); const displayFqn = useAppSelector(getDisplayFqn);
const inputElem = useRef<HTMLTextAreaElement | null>(null);
const chat = useAppSelector(state => { const chat = useAppSelector(state => {
const chat = state.chats.items.get(params.chatId, ImmutableMap()).toJS() as any; const chat = state.chats.items.get(params.chatId, ImmutableMap()).toJS() as any;
return getChat(state, chat); return getChat(state, chat);
}); });
const focusInput = () => {
inputElem.current?.focus();
};
const handleInputRef = (el: HTMLTextAreaElement) => {
inputElem.current = el;
focusInput();
};
const markRead = () => { const markRead = () => {
if (!chat) return; if (!chat) return;
dispatch(markChatRead(chat.id)); dispatch(markChatRead(chat.id));
@ -57,11 +47,7 @@ const ChatRoom: React.FC<IChatRoom> = ({ params }) => {
return ( return (
<Column label={`@${getAcct(chat.account as any, displayFqn)}`}> <Column label={`@${getAcct(chat.account as any, displayFqn)}`}>
<ChatBox <ChatBox chat={chat as any} autosize />
chat={chat as any}
onSetInputRef={handleInputRef}
autosize
/>
</Column> </Column>
); );
}; };

Wyświetl plik

@ -1,20 +1,13 @@
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { OrderedSet as ImmutableOrderedSet } from 'immutable'; import React, { MutableRefObject, useState } from 'react';
import React, { MutableRefObject, useEffect, useRef, useState } from 'react';
import { useIntl, defineMessages } from 'react-intl'; import { useIntl, defineMessages } from 'react-intl';
import {
sendChatMessage,
markChatRead,
} from 'soapbox/actions/chats';
import { uploadMedia } from 'soapbox/actions/media'; import { uploadMedia } from 'soapbox/actions/media';
import { openModal } from 'soapbox/actions/modals'; import { HStack, IconButton, Stack, Text, Textarea } from 'soapbox/components/ui';
import { initReport } from 'soapbox/actions/reports';
import { Avatar, Button, HStack, Icon, IconButton, Input, Stack, Text, Textarea } from 'soapbox/components/ui';
import UploadProgress from 'soapbox/components/upload-progress'; import UploadProgress from 'soapbox/components/upload-progress';
import UploadButton from 'soapbox/features/compose/components/upload_button'; import UploadButton from 'soapbox/features/compose/components/upload_button';
import { useAppSelector, useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch, useOwnAccount } from 'soapbox/hooks';
import { IChat, IChatMessage, useChat } from 'soapbox/queries/chats'; import { IChat, useChat } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client'; import { queryClient } from 'soapbox/queries/client';
import { truncateFilename } from 'soapbox/utils/media'; import { truncateFilename } from 'soapbox/utils/media';
@ -29,7 +22,6 @@ const fileKeyGen = (): number => Math.floor((Math.random() * 0x10000));
interface IChatBox { interface IChatBox {
chat: IChat, chat: IChat,
onSetInputRef: (el: HTMLTextAreaElement) => void,
autosize?: boolean, autosize?: boolean,
inputRef?: MutableRefObject<HTMLTextAreaElement> inputRef?: MutableRefObject<HTMLTextAreaElement>
} }
@ -38,21 +30,19 @@ interface IChatBox {
* Chat UI with just the messages and textarea. * Chat UI with just the messages and textarea.
* Reused between floating desktop chats and fullscreen/mobile chats. * Reused between floating desktop chats and fullscreen/mobile chats.
*/ */
const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }) => { const ChatBox: React.FC<IChatBox> = ({ chat, autosize, inputRef }) => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const chatMessageIds = useAppSelector(state => state.chat_message_lists.get(chat.id, ImmutableOrderedSet<string>()));
const account = useOwnAccount(); const account = useOwnAccount();
const { createChatMessage, markChatAsRead, acceptChat } = useChat(chat.id); const { createChatMessage, acceptChat } = useChat(chat.id);
const [content, setContent] = useState<string>(''); const [content, setContent] = useState<string>('');
const [attachment, setAttachment] = useState<any>(undefined); const [attachment, setAttachment] = useState<any>(undefined);
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0); const [uploadProgress, setUploadProgress] = useState(0);
const [resetFileKey, setResetFileKey] = useState<number>(fileKeyGen()); const [resetFileKey, setResetFileKey] = useState<number>(fileKeyGen());
const [hasErrorSubmittingMessage, setErrorSubmittingMessage] = useState<boolean>(false);
const inputElem = useRef<HTMLTextAreaElement | null>(null);
const isSubmitDisabled = content.length === 0 && !attachment; const isSubmitDisabled = content.length === 0 && !attachment;
@ -99,6 +89,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
onError: (_error: any, _newData: any, context: any) => { onError: (_error: any, _newData: any, context: any) => {
setContent(context.prevContent); setContent(context.prevContent);
queryClient.setQueryData(['chats', 'messages', chat.id], context.prevChatMessages); queryClient.setQueryData(['chats', 'messages', chat.id], context.prevChatMessages);
setErrorSubmittingMessage(true);
}, },
// Always refetch after error or success: // Always refetch after error or success:
onSuccess: () => { onSuccess: () => {
@ -112,6 +103,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
setIsUploading(false); setIsUploading(false);
setUploadProgress(0); setUploadProgress(0);
setResetFileKey(fileKeyGen()); setResetFileKey(fileKeyGen());
setErrorSubmittingMessage(false);
}; };
const sendMessage = () => { const sendMessage = () => {
@ -217,11 +209,11 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
<Stack className='overflow-hidden flex flex-grow' onMouseOver={handleMouseOver}> <Stack className='overflow-hidden flex flex-grow' onMouseOver={handleMouseOver}>
<div className='flex-grow h-full overflow-hidden flex justify-center'> <div className='flex-grow h-full overflow-hidden flex justify-center'>
<ChatMessageList chat={chat} autosize /> <ChatMessageList chat={chat} autosize />
</div > </div>
<div className='mt-auto p-4 shadow-3xl'> <div className='mt-auto pt-4 px-4 shadow-3xl'>
<HStack alignItems='center' justifyContent='between' space={4}> <HStack alignItems='center' justifyContent='between' space={4}>
<div className='flex-grow'> <Stack grow>
<Textarea <Textarea
autoFocus autoFocus
ref={inputRef} ref={inputRef}
@ -233,7 +225,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
autoGrow autoGrow
maxRows={5} maxRows={5}
/> />
</div> </Stack>
<IconButton <IconButton
src={require('@tabler/icons/send.svg')} src={require('@tabler/icons/send.svg')}
@ -243,8 +235,24 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
onClick={sendMessage} onClick={sendMessage}
/> />
</HStack> </HStack>
<HStack alignItems='center' className='h-5' space={1}>
{hasErrorSubmittingMessage && (
<>
<Text theme='danger' size='xs'>
Message failed to send.
</Text>
<button onClick={sendMessage} className='flex hover:underline'>
<Text theme='primary' size='xs' tag='span'>
Retry?
</Text>
</button>
</>
)}
</HStack>
</div> </div>
</Stack > </Stack>
// {renderAttachment()} // {renderAttachment()}
// {isUploading && ( // {isUploading && (
// <UploadProgress progress={uploadProgress * 100} /> // <UploadProgress progress={uploadProgress * 100} />

Wyświetl plik

@ -74,7 +74,7 @@ const ChatWindow = () => {
/> />
<Stack className='overflow-hidden flex-grow h-full' space={2}> <Stack className='overflow-hidden flex-grow h-full' space={2}>
<ChatBox chat={chat} inputRef={inputRef as any} onSetInputRef={() => null} /> <ChatBox chat={chat} inputRef={inputRef as any} />
</Stack> </Stack>
</> </>
); );

Wyświetl plik

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import RelativeTimestamp from 'soapbox/components/relative_timestamp'; import RelativeTimestamp from 'soapbox/components/relative-timestamp';
import { Avatar, HStack, Stack, Text } from 'soapbox/components/ui'; import { Avatar, HStack, Stack, Text } from 'soapbox/components/ui';
import VerificationBadge from 'soapbox/components/verification_badge'; import VerificationBadge from 'soapbox/components/verification_badge';