From 5a081466feec26bfd48c3fdc46de3fb27fafb038 Mon Sep 17 00:00:00 2001 From: Pavel <14926950+prichodko@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:48:10 +0200 Subject: [PATCH] Add sorting of chats (#302) * add sorting of chats * simplify sidebar structure * move categories at the bottom * fix duplicate fields * disable context menu for chat group * rename chat group to chat category * fix category font color * show active chat even when category closed --- .../components/channels/channel-group.tsx | 64 --------------- .../components/channels/channel-item.tsx | 22 ------ .../components/channels/index.tsx | 27 ------- .../components/chats/chat-category.tsx | 78 +++++++++++++++++++ .../index.tsx => chats/chat-item.tsx} | 26 +++---- .../main-sidebar/components/chats/index.tsx | 26 +++++++ .../src/components/main-sidebar/index.tsx | 4 +- packages/status-react/src/protocol/index.tsx | 2 +- .../src/protocol/use-active-chat.tsx | 12 +++ .../status-react/src/protocol/use-chats.tsx | 27 ------- .../src/protocol/use-sorted-chats.tsx | 77 ++++++++++++++++++ 11 files changed, 209 insertions(+), 156 deletions(-) delete mode 100644 packages/status-react/src/components/main-sidebar/components/channels/channel-group.tsx delete mode 100644 packages/status-react/src/components/main-sidebar/components/channels/channel-item.tsx delete mode 100644 packages/status-react/src/components/main-sidebar/components/channels/index.tsx create mode 100644 packages/status-react/src/components/main-sidebar/components/chats/chat-category.tsx rename packages/status-react/src/components/main-sidebar/components/{sidebar-item/index.tsx => chats/chat-item.tsx} (74%) create mode 100644 packages/status-react/src/components/main-sidebar/components/chats/index.tsx create mode 100644 packages/status-react/src/protocol/use-active-chat.tsx delete mode 100644 packages/status-react/src/protocol/use-chats.tsx create mode 100644 packages/status-react/src/protocol/use-sorted-chats.tsx diff --git a/packages/status-react/src/components/main-sidebar/components/channels/channel-group.tsx b/packages/status-react/src/components/main-sidebar/components/channels/channel-group.tsx deleted file mode 100644 index fbe8986..0000000 --- a/packages/status-react/src/components/main-sidebar/components/channels/channel-group.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react' - -import * as Collapsible from '@radix-ui/react-collapsible' - -import { BellIcon } from '../../../../icons/bell-icon' -import { ChevronDownIcon } from '../../../../icons/chevron-down-icon' -import { styled } from '../../../../styles/config' -import { ContextMenu, ContextMenuTrigger, Text } from '../../../../system' - -interface Props { - name: string - children: React.ReactNode -} - -export const ChannelGroup = (props: Props) => { - const { name, children } = props - - return ( - - - - - {name} - - - - {children} - - - }> - For 15 min - For 1 hour - For 8 hours - For 24 hours - Until I turn it back on - - }>Mark as Read - - - ) -} - -const CollapsibleTrigger = styled(Collapsible.Trigger, { - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: 8, - borderRadius: 8, - height: 34, - color: '$accent-1', - - '&:hover': { - background: '$gray-3', - }, - - '&[aria-expanded="true"] svg': { - transform: 'rotate(180deg)', - }, -}) - -const CollapsibleContent = styled(Collapsible.Content, { - overflow: 'hidden', -}) diff --git a/packages/status-react/src/components/main-sidebar/components/channels/channel-item.tsx b/packages/status-react/src/components/main-sidebar/components/channels/channel-item.tsx deleted file mode 100644 index f34e544..0000000 --- a/packages/status-react/src/components/main-sidebar/components/channels/channel-item.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' - -// import { ChatMenu } from '../../../../components/chat-menu' -// import { ContextMenuTrigger } from '../../../../system' -import { SidebarItem } from '../sidebar-item' - -import type { SidebarItemProps } from '../sidebar-item' - -interface Props extends SidebarItemProps { - children: string -} - -export const ChannelItem = (props: Props) => { - const { children, ...sidebarItemProps } = props - - return ( - // - #{children} - // - // - ) -} diff --git a/packages/status-react/src/components/main-sidebar/components/channels/index.tsx b/packages/status-react/src/components/main-sidebar/components/channels/index.tsx deleted file mode 100644 index 7e28d9a..0000000 --- a/packages/status-react/src/components/main-sidebar/components/channels/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' - -import { useChats } from '../../../../protocol' -import { Box } from '../../../../system' -// import { ChannelGroup } from './channel-group' -import { ChannelItem } from './channel-item' - -export const Channels = () => { - const chats = useChats() - - return ( - - {chats.map(chat => ( - - {chat.identity!.displayName} - - ))} - - ) -} diff --git a/packages/status-react/src/components/main-sidebar/components/chats/chat-category.tsx b/packages/status-react/src/components/main-sidebar/components/chats/chat-category.tsx new file mode 100644 index 0000000..784a30f --- /dev/null +++ b/packages/status-react/src/components/main-sidebar/components/chats/chat-category.tsx @@ -0,0 +1,78 @@ +import React, { useState } from 'react' + +import * as Collapsible from '@radix-ui/react-collapsible' + +import { ChevronDownIcon } from '../../../../icons/chevron-down-icon' +import { useActiveChat } from '../../../../protocol/use-active-chat' +import { styled } from '../../../../styles/config' + +import type { ChatItem } from './chat-item' + +interface Props { + name: string + children: React.ReactNode +} + +export const ChatCategory = (props: Props) => { + const { name, children } = props + const chat = useActiveChat() + + const [open, setOpen] = useState(true) + + // show active chat even though the category is closed + const activeChild = React.Children.toArray(children).find(child => { + if (React.isValidElement>(child)) { + return child.props.chat.id === chat?.uuid + } + }) + + return ( + <> + + + {name} + + + {children} + + {open === false && activeChild} + + ) +} + +/* + + }> + For 15 min + For 1 hour + For 8 hours + For 24 hours + Until I turn it back on + + }>Mark as Read + + */ + +const CollapsibleTrigger = styled(Collapsible.Trigger, { + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: 8, + borderRadius: 8, + height: 34, + fontWeight: '$500', + color: '$accent-4', + + '&:hover': { + background: '$gray-3', + }, + + '&[aria-expanded="true"] svg': { + transform: 'rotate(180deg)', + }, +}) + +const CollapsibleContent = styled(Collapsible.Content, { + overflow: 'hidden', +}) diff --git a/packages/status-react/src/components/main-sidebar/components/sidebar-item/index.tsx b/packages/status-react/src/components/main-sidebar/components/chats/chat-item.tsx similarity index 74% rename from packages/status-react/src/components/main-sidebar/components/sidebar-item/index.tsx rename to packages/status-react/src/components/main-sidebar/components/chats/chat-item.tsx index ce0822c..fa71d6a 100644 --- a/packages/status-react/src/components/main-sidebar/components/sidebar-item/index.tsx +++ b/packages/status-react/src/components/main-sidebar/components/chats/chat-item.tsx @@ -5,35 +5,35 @@ import { NavLink } from 'react-router-dom' import { styled } from '../../../../styles/config' import { Avatar } from '../../../../system' +import type { Chat } from '../../../../protocol/use-sorted-chats' import type { Ref } from 'react' interface Props { - to: string - muted: boolean - unread: boolean - children: React.ReactNode - name?: string - color?: string + chat: Chat } -const SidebarItem = (props: Props, ref: Ref) => { - const { muted, unread, children, name, color, ...buttonProps } = props +const ChatItem = (props: Props, ref: Ref) => { + const { chat } = props + + const muted = false + const unread = false + + const { color, displayName } = chat.identity! return ( - - {children} + #{displayName} ) } -const _SidebarItem = forwardRef(SidebarItem) +const _ChatItem = forwardRef(ChatItem) -export { _SidebarItem as SidebarItem } +export { _ChatItem as ChatItem } export type SidebarItemProps = Omit const Link = styled(NavLink, { diff --git a/packages/status-react/src/components/main-sidebar/components/chats/index.tsx b/packages/status-react/src/components/main-sidebar/components/chats/index.tsx new file mode 100644 index 0000000..598bf95 --- /dev/null +++ b/packages/status-react/src/components/main-sidebar/components/chats/index.tsx @@ -0,0 +1,26 @@ +import React from 'react' + +import { useSortedChats } from '../../../../protocol' +import { Box } from '../../../../system' +import { ChatCategory } from './chat-category' +import { ChatItem } from './chat-item' + +export const Chats = () => { + const { categories, chats } = useSortedChats() + + return ( + + {chats.map(chat => ( + + ))} + + {categories.map(category => ( + + {category.chats.map(chat => ( + + ))} + + ))} + + ) +} diff --git a/packages/status-react/src/components/main-sidebar/index.tsx b/packages/status-react/src/components/main-sidebar/index.tsx index 4f8ecd6..d14ce63 100644 --- a/packages/status-react/src/components/main-sidebar/index.tsx +++ b/packages/status-react/src/components/main-sidebar/index.tsx @@ -4,7 +4,7 @@ import { useAppState } from '../../contexts/app-context' import { useAccount } from '../../protocol' import { styled } from '../../styles/config' import { Separator } from '../../system' -import { Channels } from './components/channels' +import { Chats } from './components/chats' import { CommunityInfo } from './components/community-info' import { GetStarted } from './components/get-started' @@ -19,7 +19,7 @@ export const MainSidebar = () => { return ( - + {!account && ( <> diff --git a/packages/status-react/src/protocol/index.tsx b/packages/status-react/src/protocol/index.tsx index 3bc9445..54f7646 100644 --- a/packages/status-react/src/protocol/index.tsx +++ b/packages/status-react/src/protocol/index.tsx @@ -3,8 +3,8 @@ export type { Account } from './use-account' export { useAccount } from './use-account' export type { Chat } from './use-chat' export { useChat } from './use-chat' -export { useChats } from './use-chats' export type { Member } from './use-members' export { useMembers } from './use-members' export type { Message, Reaction, Reactions } from './use-messages' export { useMessages } from './use-messages' +export { useSortedChats } from './use-sorted-chats' diff --git a/packages/status-react/src/protocol/use-active-chat.tsx b/packages/status-react/src/protocol/use-active-chat.tsx new file mode 100644 index 0000000..3107874 --- /dev/null +++ b/packages/status-react/src/protocol/use-active-chat.tsx @@ -0,0 +1,12 @@ +import { useMatch } from 'react-router-dom' + +import { useProtocol } from './provider' + +export const useActiveChat = () => { + const { client } = useProtocol() + + const { params } = useMatch(':id')! + const chatId = params.id! + + return client.community.getChat(chatId) +} diff --git a/packages/status-react/src/protocol/use-chats.tsx b/packages/status-react/src/protocol/use-chats.tsx deleted file mode 100644 index 9c4dd1d..0000000 --- a/packages/status-react/src/protocol/use-chats.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useMemo } from 'react' - -import { useProtocol } from './provider' - -import type { Community } from '@status-im/js' - -export type Chat = Community['description']['chats'][0] & { - id: string -} - -export const useChats = (): Chat[] => { - const { community } = useProtocol() - - return useMemo(() => { - return Object.entries(community.chats) - .map(([chatId, chat]) => ({ id: chatId, ...chat })) - .sort((a, b) => { - if (a.position < b.position) { - return -1 - } - if (a.position > b.position) { - return 1 - } - return 0 - }) - }, [community]) -} diff --git a/packages/status-react/src/protocol/use-sorted-chats.tsx b/packages/status-react/src/protocol/use-sorted-chats.tsx new file mode 100644 index 0000000..093656b --- /dev/null +++ b/packages/status-react/src/protocol/use-sorted-chats.tsx @@ -0,0 +1,77 @@ +import { useMemo } from 'react' + +import { useProtocol } from './provider' + +import type { Community } from '@status-im/js' + +export type Chat = Community['description']['chats'][0] & { + id: string +} + +export type Category = { + id: string + name: string + position: number + chats: Chat[] +} + +type Result = { + categories: Category[] + chats: Chat[] +} + +function sortByPosition(items: T[]): T[] { + items.sort((a, b) => { + if (a.position < b.position) { + return -1 + } + if (a.position > b.position) { + return 1 + } + return 0 + }) + + return items +} + +export const useSortedChats = (): Result => { + const { community } = useProtocol() + + return useMemo(() => { + const categoryChats: Record = {} + + const chats = Object.entries(community.chats).reduce( + (acc, [chatId, chat]) => { + const parsedChat: Chat = { + id: chatId, + ...chat, + } + + if (chat.categoryId && community.categories[chat.categoryId]) { + categoryChats[chat.categoryId] ??= [] + categoryChats[chat.categoryId].push(parsedChat) + } else { + acc.push(parsedChat) + } + + return acc + }, + [] + ) + + const categories = Object.entries(categoryChats).map(([id, chats]) => { + const { name, position } = community.categories[id] + return { + id, + name, + position, + chats: sortByPosition(chats), + } + }) + + return { + categories: sortByPosition(categories), + chats: sortByPosition(chats), + } + }, [community]) +}