Add Message List Intro component
This commit is contained in:
parent
396a1f1f46
commit
a2e2d60fc7
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
</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} />
|
||||
|
|
|
@ -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;
|
|
@ -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 ? (
|
||||
<>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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']);
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue