Add Message List Intro component

alex-chats
Justin 2022-08-17 15:48:04 -04:00
rodzic 396a1f1f46
commit a2e2d60fc7
7 zmienionych plików z 157 dodań i 81 usunięć

Wyświetl plik

@ -25,7 +25,7 @@ const useButtonStyles = ({
tertiary:
'bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500',
accent: 'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300',
danger: 'border-transparent bg-danger-600 text-gray-100 hover:bg-danger-500 dark:hover:bg-danger-700 focus:bg-danger-600 dark:focus:bg-danger-600',
danger: 'border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:bg-danger-800 dark:focus:bg-danger-600',
transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80',
outline: 'border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10',
};

Wyświetl plik

@ -1,8 +1,8 @@
import React, { createContext, useContext, useState } from 'react';
import React, { createContext, useContext, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { toggleMainWindow } from 'soapbox/actions/chats';
import { useSettings } from 'soapbox/hooks';
import { useOwnAccount, useSettings } from 'soapbox/hooks';
import type { IChat } from 'soapbox/queries/chats';
@ -12,23 +12,35 @@ const ChatContext = createContext<any>({
chat: null,
isOpen: false,
isEditing: false,
needsAcceptance: false,
});
const ChatProvider: React.FC = ({ children }) => {
const dispatch = useDispatch();
const settings = useSettings();
const account = useOwnAccount();
const [chat, setChat] = useState<IChat | null>(null);
const [isEditing, setEditing] = useState<boolean>(false);
const mainWindowState = settings.getIn(['chats', 'mainWindow']) as WindowState;
const needsAcceptance = !chat?.accepted && chat?.created_by_account !== account?.id;
const isOpen = mainWindowState === 'open';
const toggleChatPane = () => dispatch(toggleMainWindow());
const value = useMemo(() => ({
chat,
setChat,
needsAcceptance,
isOpen,
isEditing,
setEditing,
toggleChatPane,
}), [chat, needsAcceptance, isOpen, isEditing]);
return (
<ChatContext.Provider value={{ chat, setChat, isOpen, isEditing, setEditing, toggleChatPane }}>
<ChatContext.Provider value={value}>
{children}
</ChatContext.Provider>
);
@ -38,6 +50,7 @@ interface IChatContext {
chat: IChat | null
isOpen: boolean
isEditing: boolean
needsAcceptance: boolean
setChat: React.Dispatch<React.SetStateAction<IChat | null>>
setEditing: React.Dispatch<React.SetStateAction<boolean>>
toggleChatPane(): void

Wyświetl plik

@ -44,7 +44,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
const chatMessageIds = useAppSelector(state => state.chat_message_lists.get(chat.id, ImmutableOrderedSet<string>()));
const account = useOwnAccount();
const { createChatMessage, markChatAsRead, acceptChat, deleteChat } = useChat(chat.id);
const { createChatMessage, markChatAsRead, acceptChat } = useChat(chat.id);
const [content, setContent] = useState<string>('');
const [attachment, setAttachment] = useState<any>(undefined);
@ -54,8 +54,6 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
const inputElem = useRef<HTMLTextAreaElement | null>(null);
const needsAcceptance = !chat.accepted && chat.created_by_account !== account?.id;
const isSubmitDisabled = content.length === 0 && !attachment;
// TODO: needs last_read_id param
@ -188,23 +186,6 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
});
};
const handleLeaveChat = () => {
dispatch(openModal('CONFIRM', {
heading: 'Leave Chat',
message: 'Are you sure you want to leave this chat? This conversation will be removed from your inbox.',
confirm: 'Leave Chat',
confirmationTheme: 'primary',
onConfirm: () => {
deleteChat.mutate();
},
}));
};
const handleReportChat = () => {
dispatch(initReport(chat.account));
acceptChat.mutate();
};
const renderAttachment = () => {
if (!attachment) return null;
@ -240,53 +221,8 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
return (
<Stack className='overflow-hidden flex flex-grow' onMouseOver={handleMouseOver}>
<div className='flex-grow h-full overflow-hidden flex justify-center'>
{needsAcceptance ? (
<Stack justifyContent='center' alignItems='center' space={5} className='w-3/4 mx-auto'>
<Stack alignItems='center' space={2}>
<Avatar src={chat.account.avatar_static} size={75} />
<Text size='lg' align='center'>
<Text tag='span' weight='semibold'>@{chat.account.acct}</Text>
{' '}
<Text tag='span'>wants to start a chat with you</Text>
</Text>
</Stack>
<Stack space={2} className='w-full'>
<Button
theme='primary'
block
onClick={() => {
acceptChat.mutate();
inputRef?.current?.focus();
}}
disabled={acceptChat.isLoading}
>
Accept
</Button>
<HStack alignItems='center' space={2} className='w-full'>
<Button
theme='accent'
block
onClick={handleLeaveChat}
>
Leave chat
</Button>
<Button
theme='secondary'
block
onClick={handleReportChat}
>
Report
</Button>
</HStack>
</Stack>
</Stack>
) : (
<ChatMessageList chatMessageIds={chatMessageIds} chat={chat} autosize />
)}
</div>
<ChatMessageList chatMessageIds={chatMessageIds} chat={chat} autosize />
</div >
<div className='mt-auto p-4 shadow-3xl'>
<HStack alignItems='center' justifyContent='between' space={4}>
@ -312,7 +248,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, onSetInputRef, autosize, inputRef }
/>
</HStack>
</div>
</Stack>
</Stack >
// {renderAttachment()}
// {isUploading && (
// <UploadProgress progress={uploadProgress * 100} />

Wyświetl plik

@ -0,0 +1,109 @@
import classNames from 'classnames';
import React from 'react';
import { openModal } from 'soapbox/actions/modals';
import { initReport } from 'soapbox/actions/reports';
import { Avatar, Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui';
import { useChatContext } from 'soapbox/contexts/chat-context';
import { useAppDispatch } from 'soapbox/hooks';
import { useChat } from 'soapbox/queries/chats';
const ChatMessageListIntro = () => {
const dispatch = useAppDispatch();
const { chat, needsAcceptance } = useChatContext();
const { acceptChat, deleteChat } = useChat(chat?.id as string);
const handleLeaveChat = () => {
dispatch(openModal('CONFIRM', {
heading: 'Leave Chat',
message: 'Are you sure you want to leave this chat? This conversation will be removed from your inbox.',
confirm: 'Leave Chat',
confirmationTheme: 'primary',
onConfirm: () => {
deleteChat.mutate();
},
}));
};
const handleReportChat = () => {
dispatch(initReport(chat?.account));
acceptChat.mutate();
};
if (!chat) {
return null;
}
return (
<Stack
justifyContent='center'
alignItems='center'
space={4}
className={
classNames({
'w-3/4 mx-auto': needsAcceptance,
'mt-6': true,
})
}
>
<Stack alignItems='center' space={2}>
<Avatar src={chat.account.avatar_static} size={75} />
<Text size='lg' align='center'>
{needsAcceptance ? (
<>
<Text tag='span' weight='semibold'>@{chat.account.acct}</Text>
{' '}
<Text tag='span'>wants to start a chat with you</Text>
</>
) : (
<Text tag='span' weight='semibold'>@{chat.account.acct}</Text>
)}
</Text>
</Stack>
{needsAcceptance ? (
<Stack space={2} className='w-full'>
<Button
theme='primary'
block
onClick={() => {
acceptChat.mutate();
// inputRef?.current?.focus();
}}
disabled={acceptChat.isLoading}
>
Accept
</Button>
<HStack alignItems='center' space={2} className='w-full'>
<Button
theme='danger'
block
onClick={handleLeaveChat}
>
Leave chat
</Button>
<Button
theme='secondary'
block
onClick={handleReportChat}
>
Report
</Button>
</HStack>
</Stack>
) : (
<HStack justifyContent='center' alignItems='center' space={1} className='flex-shrink-0'>
<Icon src={require('@tabler/icons/clock.svg')} className='text-gray-600 w-4 h-4' />
<Text size='sm' theme='muted'>
Messages older than 15 days are deleted.
</Text>
</HStack>
)}
</Stack>
);
};
export default ChatMessageListIntro;

Wyświetl plik

@ -13,9 +13,10 @@ import { createSelector } from 'reselect';
import { fetchChatMessages, deleteChatMessage } from 'soapbox/actions/chats';
import { openModal } from 'soapbox/actions/modals';
import { initReportById } from 'soapbox/actions/reports';
import { Avatar, HStack, IconButton, Spinner, Stack, Text } from 'soapbox/components/ui';
import { initReport, initReportById } from 'soapbox/actions/reports';
import { Avatar, Button, HStack, IconButton, Spinner, Stack, Text } from 'soapbox/components/ui';
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
import { useChatContext } from 'soapbox/contexts/chat-context';
import emojify from 'soapbox/features/emoji/emoji';
import PlaceholderChat from 'soapbox/features/placeholder/components/placeholder_chat';
import Bundle from 'soapbox/features/ui/components/bundle';
@ -25,6 +26,8 @@ import { IChat, IChatMessage, useChat, useChatMessages } from 'soapbox/queries/c
import { queryClient } from 'soapbox/queries/client';
import { onlyEmoji } from 'soapbox/utils/rich_content';
import ChatMessageListIntro from './chat-message-list-intro';
import type { Menu } from 'soapbox/components/dropdown_menu';
import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities';
@ -83,13 +86,14 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, chatMessageIds, aut
const [initialLoad, setInitialLoad] = useState(true);
const [scrollPosition, setScrollPosition] = useState(0);
const { deleteChatMessage } = useChat(chat.id);
const { needsAcceptance } = useChatContext();
const { deleteChatMessage, acceptChat, deleteChat } = useChat(chat.id);
const { data: chatMessages, isLoading, isFetching, isFetched, fetchNextPage, isFetchingNextPage, isPlaceholderData } = useChatMessages(chat.id);
const formattedChatMessages = chatMessages || [];
const me = useAppSelector(state => state.me);
const node = useRef<HTMLDivElement>(null);
const messagesEnd = useRef<HTMLDivElement>(null);
const lastComputedScroll = useRef<number | undefined>(undefined);
@ -97,6 +101,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, chatMessageIds, aut
const initialCount = useMemo(() => formattedChatMessages.length, []);
const handleDeleteMessage = useMutation((chatMessageId: string) => deleteChatMessage(chatMessageId), {
onSettled: () => {
queryClient.invalidateQueries(['chats', 'messages', chat.id]);
@ -399,7 +404,11 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, chatMessageIds, aut
}
return (
<div className='h-full flex flex-col px-4 flex-grow overflow-y-scroll' onScroll={handleScroll} ref={node}> {/* style={{ height: autosize ? 'calc(100vh - 16rem)' : undefined }} */}
<div className='h-full flex flex-col px-4 flex-grow overflow-y-scroll space-y-6' onScroll={handleScroll} ref={node}> {/* style={{ height: autosize ? 'calc(100vh - 16rem)' : undefined }} */}
{!isLoading ? (
<ChatMessageListIntro />
) : null}
<div className='flex-grow flex flex-col justify-end space-y-4'>
{isLoading ? (
<>

Wyświetl plik

@ -10,7 +10,7 @@ import ChatSettings from './chat-settings';
/** Floating desktop chat window. */
const ChatWindow = () => {
const { chat, setChat, isOpen, isEditing, setEditing, toggleChatPane } = useChatContext();
const { chat, setChat, isOpen, isEditing, needsAcceptance, setEditing, toggleChatPane } = useChatContext();
const inputRef = useRef<HTMLTextAreaElement>();
@ -23,6 +23,14 @@ const ChatWindow = () => {
const openChatSettings = () => setEditing(true);
const secondaryAction = () => {
if (needsAcceptance) {
return undefined;
}
return isOpen ? openChatSettings : openAndFocusChat;
};
if (!chat) return null;
if (isEditing) {
@ -58,7 +66,7 @@ const ChatWindow = () => {
</HStack>
</HStack>
}
secondaryAction={isOpen ? openChatSettings : openAndFocusChat}
secondaryAction={secondaryAction()}
secondaryActionIcon={isOpen ? require('@tabler/icons/info-circle.svg') : require('@tabler/icons/edit.svg')}
isToggleable={!isOpen}
isOpen={isOpen}

Wyświetl plik

@ -93,7 +93,7 @@ const useChats = () => {
const useChat = (chatId: string) => {
const api = useApi();
const { setChat } = useChatContext();
const { setChat, setEditing } = useChatContext();
const markChatAsRead = () => api.post<IChat>(`/api/v1/pleroma/chats/${chatId}/read`);
@ -114,6 +114,7 @@ const useChat = (chatId: string) => {
const deleteChat = useMutation(() => api.delete<IChat>(`/api/v1/pleroma/chats/${chatId}`), {
onSuccess(response) {
setChat(null);
setEditing(false);
queryClient.invalidateQueries(['chats', 'messages', chatId]);
queryClient.invalidateQueries(['chats']);
},