Add Chat Settings
This commit is contained in:
parent
d2d64e4ec4
commit
396a1f1f46
5 changed files with 145 additions and 11 deletions
|
@ -11,13 +11,16 @@ type WindowState = 'open' | 'minimized';
|
||||||
const ChatContext = createContext<any>({
|
const ChatContext = createContext<any>({
|
||||||
chat: null,
|
chat: null,
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
|
isEditing: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const ChatProvider: React.FC = ({ children }) => {
|
const ChatProvider: React.FC = ({ children }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
const [chat, setChat] = useState<IChat | null>();
|
const [chat, setChat] = useState<IChat | null>(null);
|
||||||
|
const [isEditing, setEditing] = useState<boolean>(false);
|
||||||
|
|
||||||
const mainWindowState = settings.getIn(['chats', 'mainWindow']) as WindowState;
|
const mainWindowState = settings.getIn(['chats', 'mainWindow']) as WindowState;
|
||||||
|
|
||||||
const isOpen = mainWindowState === 'open';
|
const isOpen = mainWindowState === 'open';
|
||||||
|
@ -25,14 +28,18 @@ const ChatProvider: React.FC = ({ children }) => {
|
||||||
const toggleChatPane = () => dispatch(toggleMainWindow());
|
const toggleChatPane = () => dispatch(toggleMainWindow());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatContext.Provider value={{ chat, setChat, isOpen, toggleChatPane }}>{children}</ChatContext.Provider>
|
<ChatContext.Provider value={{ chat, setChat, isOpen, isEditing, setEditing, toggleChatPane }}>
|
||||||
|
{children}
|
||||||
|
</ChatContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IChatContext {
|
interface IChatContext {
|
||||||
chat: IChat | null
|
chat: IChat | null
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
isEditing: boolean
|
||||||
setChat: React.Dispatch<React.SetStateAction<IChat | null>>
|
setChat: React.Dispatch<React.SetStateAction<IChat | null>>
|
||||||
|
setEditing: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
toggleChatPane(): void
|
toggleChatPane(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ const ChatPane = () => {
|
||||||
<Text weight='semibold' truncate>{account.display_name}</Text>
|
<Text weight='semibold' truncate>{account.display_name}</Text>
|
||||||
{account.verified && <VerificationBadge />}
|
{account.verified && <VerificationBadge />}
|
||||||
</div>
|
</div>
|
||||||
<Text theme='muted' truncate>{account.acct}</Text>
|
<Text theme='muted' truncate>@{account.acct}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</HStack>
|
</HStack>
|
||||||
</button>
|
</button>
|
||||||
|
@ -128,10 +128,10 @@ const ChatPane = () => {
|
||||||
|
|
||||||
<Stack alignItems='start'>
|
<Stack alignItems='start'>
|
||||||
<div className='flex items-center space-x-1 flex-grow'>
|
<div className='flex items-center space-x-1 flex-grow'>
|
||||||
<Text weight='semibold' truncate>{chat.account?.display_name}</Text>
|
<Text weight='bold' size='sm' truncate>{chat.account?.display_name}</Text>
|
||||||
{chat.account?.verified && <VerificationBadge />}
|
{chat.account?.verified && <VerificationBadge />}
|
||||||
</div>
|
</div>
|
||||||
<Text theme='muted' truncate>{chat.account?.acct}</Text>
|
<Text size='sm' weight='medium' theme='muted' truncate>@{chat.account?.acct}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</HStack>
|
</HStack>
|
||||||
</button>
|
</button>
|
||||||
|
|
119
app/soapbox/features/chats/components/chat-settings.tsx
Normal file
119
app/soapbox/features/chats/components/chat-settings.tsx
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { blockAccount } from 'soapbox/actions/accounts';
|
||||||
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
|
import { initReport } from 'soapbox/actions/reports';
|
||||||
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
|
import { Avatar, Divider, HStack, Icon, Stack, Text, Toggle } from 'soapbox/components/ui';
|
||||||
|
import { useChatContext } from 'soapbox/contexts/chat-context';
|
||||||
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
import { useChat } from 'soapbox/queries/chats';
|
||||||
|
|
||||||
|
import ChatPaneHeader from './chat-pane-header';
|
||||||
|
|
||||||
|
const ChatSettings = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { chat, setEditing, toggleChatPane } = useChatContext();
|
||||||
|
const { deleteChat } = useChat(chat?.id as string);
|
||||||
|
|
||||||
|
const closeSettings = () => setEditing(false);
|
||||||
|
|
||||||
|
const minimizeChatPane = () => {
|
||||||
|
closeSettings();
|
||||||
|
toggleChatPane();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlockUser = () => {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
heading: `Block @${chat?.account.acct}`,
|
||||||
|
message: 'Blocking will prevent this profile from direct messaging you and viewing your content. You can unblock later.',
|
||||||
|
confirm: 'Block',
|
||||||
|
confirmationTheme: 'primary',
|
||||||
|
onConfirm: () => dispatch(blockAccount(chat?.account.id)),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!chat) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ChatPaneHeader
|
||||||
|
isOpen
|
||||||
|
isToggleable={false}
|
||||||
|
onToggle={minimizeChatPane}
|
||||||
|
title={
|
||||||
|
<HStack alignItems='center' space={2}>
|
||||||
|
<button onClick={closeSettings}>
|
||||||
|
<Icon
|
||||||
|
src={require('@tabler/icons/arrow-left.svg')}
|
||||||
|
className='h-6 w-6 text-gray-600 dark:text-gray-400'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Text weight='semibold'>
|
||||||
|
Chat Details
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack space={4} className='w-5/6 mx-auto'>
|
||||||
|
<Stack alignItems='center' space={2}>
|
||||||
|
<Avatar src={chat.account.avatar_static} size={75} />
|
||||||
|
<Stack>
|
||||||
|
<Text size='lg' weight='semibold' align='center'>{chat.account.display_name}</Text>
|
||||||
|
<Text theme='primary' align='center'>@{chat.account.acct}</Text>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<List>
|
||||||
|
<ListItem label='Snooze notifications'>
|
||||||
|
<Toggle />
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Stack space={5}>
|
||||||
|
<button onClick={handleBlockUser} className='w-full flex items-center space-x-2 font-bold text-sm text-gray-700'>
|
||||||
|
<Icon src={require('@tabler/icons/ban.svg')} className='w-5 h-5 text-gray-600' />
|
||||||
|
<span>Block @{chat.account.acct}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button onClick={handleReportChat} className='w-full flex items-center space-x-2 font-bold text-sm text-gray-700'>
|
||||||
|
<Icon src={require('@tabler/icons/flag.svg')} className='w-5 h-5 text-gray-600' />
|
||||||
|
<span>Report @{chat.account.acct}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button onClick={handleLeaveChat} className='w-full flex items-center space-x-2 font-bold text-sm text-danger-600'>
|
||||||
|
<Icon src={require('@tabler/icons/logout.svg')} className='w-5 h-5 text-danger-600' />
|
||||||
|
<span>Leave chat</span>
|
||||||
|
</button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatSettings;
|
|
@ -6,21 +6,29 @@ import { useChatContext } from 'soapbox/contexts/chat-context';
|
||||||
|
|
||||||
import ChatBox from './chat-box';
|
import ChatBox from './chat-box';
|
||||||
import ChatPaneHeader from './chat-pane-header';
|
import ChatPaneHeader from './chat-pane-header';
|
||||||
|
import ChatSettings from './chat-settings';
|
||||||
|
|
||||||
/** Floating desktop chat window. */
|
/** Floating desktop chat window. */
|
||||||
const ChatWindow = () => {
|
const ChatWindow = () => {
|
||||||
const { chat, setChat, isOpen, toggleChatPane } = useChatContext();
|
const { chat, setChat, isOpen, isEditing, setEditing, toggleChatPane } = useChatContext();
|
||||||
|
|
||||||
const inputRef = useRef<HTMLTextAreaElement>();
|
const inputRef = useRef<HTMLTextAreaElement>();
|
||||||
|
|
||||||
const closeChat = () => setChat(null);
|
const closeChat = () => setChat(null);
|
||||||
|
|
||||||
const openAndFocusChat = () => {
|
const openAndFocusChat = () => {
|
||||||
toggleChatPane();
|
toggleChatPane();
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openChatSettings = () => setEditing(true);
|
||||||
|
|
||||||
if (!chat) return null;
|
if (!chat) return null;
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
return <ChatSettings />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatPaneHeader
|
<ChatPaneHeader
|
||||||
|
@ -42,16 +50,16 @@ const ChatWindow = () => {
|
||||||
|
|
||||||
<Stack alignItems='start'>
|
<Stack alignItems='start'>
|
||||||
<div className='flex items-center space-x-1 flex-grow'>
|
<div className='flex items-center space-x-1 flex-grow'>
|
||||||
<Text weight='semibold' truncate>{chat.account.display_name}</Text>
|
<Text size='sm' weight='bold' truncate>{chat.account.display_name}</Text>
|
||||||
{chat.account.verified && <VerificationBadge />}
|
{chat.account.verified && <VerificationBadge />}
|
||||||
</div>
|
</div>
|
||||||
<Text theme='muted' truncate>{chat.account.acct}</Text>
|
<Text size='sm' weight='medium' theme='primary' truncate>@{chat.account.acct}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
}
|
}
|
||||||
secondaryAction={isOpen ? undefined : openAndFocusChat}
|
secondaryAction={isOpen ? openChatSettings : openAndFocusChat}
|
||||||
secondaryActionIcon={isOpen ? undefined : require('@tabler/icons/edit.svg')}
|
secondaryActionIcon={isOpen ? require('@tabler/icons/info-circle.svg') : require('@tabler/icons/edit.svg')}
|
||||||
isToggleable={!isOpen}
|
isToggleable={!isOpen}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onToggle={toggleChatPane}
|
onToggle={toggleChatPane}
|
||||||
|
|
|
@ -18,7 +18,7 @@ const Pane: React.FC<IPane> = ({ isOpen = false, index, children, main = false }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('flex flex-col shadow-3xl bg-white dark:bg-gray-900 rounded-t-lg fixed bottom-0 right-1 w-96 z-[1000]', {
|
className={classNames('flex flex-col shadow-3xl bg-white dark:bg-gray-900 rounded-t-lg fixed bottom-0 right-1 w-96 z-[99]', {
|
||||||
'h-[550px] max-h-[100vh]': isOpen,
|
'h-[550px] max-h-[100vh]': isOpen,
|
||||||
'h-16': !isOpen,
|
'h-16': !isOpen,
|
||||||
})}
|
})}
|
||||||
|
|
Loading…
Reference in a new issue