From bb3cdfa0564883d12fed42225661b2fc0f896344 Mon Sep 17 00:00:00 2001 From: Jeffrey Yu <35394596+JeffreytheCoder@users.noreply.github.com> Date: Sun, 17 Mar 2024 05:06:57 -0700 Subject: [PATCH 1/2] Refactor sidebar components (#509) * Refactor sidebar components * Fix linting error * Format with Prettier * Add spacing & avoid scrollbar * Remove unused CSS and imports --- .../src/components/AllThreads/AllThreads.js | 181 ++++++++--------- .../AllThreads/AllThreads.module.css | 18 -- .../RoomInformation/RoomInformation.js | 42 +--- .../src/components/RoomMembers/RoomMember.js | 23 +-- .../RoomMembers/RoomMember.module.css | 19 -- .../components/SearchMessage/SearchMessage.js | 177 ++++++++--------- .../SearchMessage/SearchMessage.module.css | 19 -- .../react/src/components/Sidebar/Sidebar.js | 27 +++ .../src/components/Sidebar/Sidebar.module.css | 41 ++++ .../components/UserMentions/UserMentions.js | 182 ++++++++---------- .../UserMentions/UserMentions.module.css | 22 --- 11 files changed, 314 insertions(+), 437 deletions(-) create mode 100644 packages/react/src/components/Sidebar/Sidebar.js create mode 100644 packages/react/src/components/Sidebar/Sidebar.module.css delete mode 100644 packages/react/src/components/UserMentions/UserMentions.module.css diff --git a/packages/react/src/components/AllThreads/AllThreads.js b/packages/react/src/components/AllThreads/AllThreads.js index 389b5034b..4ab9cf52b 100644 --- a/packages/react/src/components/AllThreads/AllThreads.js +++ b/packages/react/src/components/AllThreads/AllThreads.js @@ -3,7 +3,6 @@ import { css } from '@emotion/react'; import classes from './AllThreads.module.css'; import { Icon } from '../Icon'; import { Box } from '../Box'; -import { ActionButton } from '../ActionButton'; import { useMessageStore, useUserStore, @@ -14,6 +13,7 @@ import { MessageMetrics } from '../Message/MessageMetrics'; import MessageAvatarContainer from '../Message/MessageAvatarContainer'; import MessageBodyContainer from '../Message/MessageBodyContainer'; import MessageHeader from '../Message/MessageHeader'; +import Sidebar from '../Sidebar/Sidebar'; const MessageCss = css` display: flex; @@ -65,115 +65,90 @@ const AllThreads = () => { ); return ( - - - - - - - - Threads - - - - - - + + + + + + + + {filteredThreads.length === 0 ? ( - - - + + + No threads found + - - - - {filteredThreads.length === 0 ? ( - - - - No threads found - - - ) : ( - filteredThreads.map( - (message) => - !message.t && - message.tcount && ( - - {showAvatar && ( - - )} - - + ) : ( + filteredThreads.map( + (message) => + !message.t && + message.tcount && ( + + {showAvatar && ( + + )} + + - - {message.attachments && message.attachments.length > 0 - ? message.file.name - : message.msg} - + + {message.attachments && message.attachments.length > 0 + ? message.file.name + : message.msg} + - - - - ) - ) - )} - + + + + ) + ) + )} - + ); }; diff --git a/packages/react/src/components/AllThreads/AllThreads.module.css b/packages/react/src/components/AllThreads/AllThreads.module.css index 1ae7eaf6b..f02725028 100644 --- a/packages/react/src/components/AllThreads/AllThreads.module.css +++ b/packages/react/src/components/AllThreads/AllThreads.module.css @@ -1,21 +1,3 @@ -.component { - position: fixed; - right: 0; - top: 0; - width: 350px; - height: 100%; - overflow: hidden; - background-color: white; - box-shadow: -1px 0px 5px rgb(0 0 0 / 25%); - z-index: 100; -} - -.wrapContainer { - height: 100%; - display: flex; - flex-direction: column; -} - .searchContainer { display: flex; align-items: center; diff --git a/packages/react/src/components/RoomInformation/RoomInformation.js b/packages/react/src/components/RoomInformation/RoomInformation.js index 059403dfd..dd3146f36 100644 --- a/packages/react/src/components/RoomInformation/RoomInformation.js +++ b/packages/react/src/components/RoomInformation/RoomInformation.js @@ -2,11 +2,9 @@ import React, { useContext } from 'react'; import { css } from '@emotion/react'; import { Avatar } from '../Avatar/Avatar'; import RCContext from '../../context/RCInstance'; -import classes from './RoomInformation.module.css'; import { useChannelStore } from '../../store'; -import { Icon } from '../Icon'; import { Box } from '../Box'; -import { ActionButton } from '../ActionButton'; +import Sidebar from '../Sidebar/Sidebar'; const Roominfo = () => { const { RCInstance } = useContext(RCContext); @@ -17,41 +15,17 @@ const Roominfo = () => { (state) => state.setShowChannelinfo ); - const toggleshowRoominfo = () => { - setShowChannelinfo(false); - }; - const getChannelAvatarURL = (channelname) => { const host = RCInstance.getHost(); return `${host}/avatar/${channelname}`; }; - return ( - - - - - - Room Information - - - - - - + return ( + { {channelInfo.description} - + ); }; export default Roominfo; diff --git a/packages/react/src/components/RoomMembers/RoomMember.js b/packages/react/src/components/RoomMembers/RoomMember.js index ae0d9440b..6fcb9d8d3 100644 --- a/packages/react/src/components/RoomMembers/RoomMember.js +++ b/packages/react/src/components/RoomMembers/RoomMember.js @@ -9,7 +9,7 @@ import InviteMembers from './inviteMembers/InviteMembers'; import { Button } from '../Button'; import { Box } from '../Box'; import { Icon } from '../Icon'; -import { ActionButton } from '../ActionButton'; +import Sidebar from '../Sidebar/Sidebar'; const RoomMembers = ({ members }) => { const { RCInstance } = useContext(RCContext); @@ -43,20 +43,11 @@ const RoomMembers = ({ members }) => { if (showInvite) return ; return ( - - - - - Members - - - - - + {members.map((member) => ( @@ -74,7 +65,7 @@ const RoomMembers = ({ members }) => { Invite Link )} - + ); }; export default RoomMembers; diff --git a/packages/react/src/components/RoomMembers/RoomMember.module.css b/packages/react/src/components/RoomMembers/RoomMember.module.css index 012b403e4..8883b6d2d 100644 --- a/packages/react/src/components/RoomMembers/RoomMember.module.css +++ b/packages/react/src/components/RoomMembers/RoomMember.module.css @@ -1,22 +1,3 @@ -.modal { - position: fixed; - right: 0; - top: 0; - width: 350px; - height: 100%; - overflow-x: scroll; - overflow-y: scroll; - background-color: white; - box-shadow: -1px 0px 5px rgb(0 0 0 / 25%); - z-index: 100; -} - -@media (max-width: 550px) { - .modal { - width: 100vw; - } -} - .container { display: flex; flex-direction: column; diff --git a/packages/react/src/components/SearchMessage/SearchMessage.js b/packages/react/src/components/SearchMessage/SearchMessage.js index 0e9b5557e..9e990924c 100644 --- a/packages/react/src/components/SearchMessage/SearchMessage.js +++ b/packages/react/src/components/SearchMessage/SearchMessage.js @@ -7,18 +7,14 @@ import { Markdown } from '../Markdown/index'; import { useUserStore, useSearchMessageStore } from '../../store'; import { Box } from '../Box'; import { Icon } from '../Icon'; -import { ActionButton } from '../ActionButton'; import { MessageDivider } from '../Message/MessageDivider'; import { Message } from '../Message'; +import Sidebar from '../Sidebar/Sidebar'; const Search = () => { const { RCInstance } = useContext(RCContext); const setShowSearch = useSearchMessageStore((state) => state.setShowSearch); - const toggleShowSearch = () => { - setShowSearch(false); - }; - const [text, setText] = useState(''); const [messageList, setMessageList] = useState([]); @@ -54,104 +50,83 @@ const Search = () => { !previous || !isSameDay(new Date(current.ts), new Date(previous.ts)); return ( - - - - - - - Search Messages - - - - - - - - + + + - - - - {messageList.length === 0 ? ( - - - - No results found - - - ) : ( - messageList.map((msg, index, arr) => { - const prev = arr[index + 1]; - const newDay = isMessageNewDay(msg, prev); - return ( - - {newDay && ( - - - {format(new Date(msg.ts), 'MMMM d, yyyy')} - - - )} - - - ); - }) - )} - + + + + {messageList.length === 0 ? ( + + + + No results found + + + ) : ( + messageList.map((msg, index, arr) => { + const prev = arr[index + 1]; + const newDay = isMessageNewDay(msg, prev); + return ( + + {newDay && ( + + + {format(new Date(msg.ts), 'MMMM d, yyyy')} + + + )} + + + ); + }) + )} - + ); }; export default Search; diff --git a/packages/react/src/components/SearchMessage/SearchMessage.module.css b/packages/react/src/components/SearchMessage/SearchMessage.module.css index a90e7f4c6..cb98500b9 100644 --- a/packages/react/src/components/SearchMessage/SearchMessage.module.css +++ b/packages/react/src/components/SearchMessage/SearchMessage.module.css @@ -1,22 +1,3 @@ -.searchBar { - position: fixed; - right: 0; - top: 0; - width: 350px; - height: 100%; - overflow-x: scroll; - overflow-y: scroll; - background-color: white; - box-shadow: -1px 0px 5px rgb(0 0 0 / 25%); - z-index: 100; -} - -.wrapContainer { - height: 100%; - display: flex; - flex-direction: column; -} - .container { display: flex; align-items: center; diff --git a/packages/react/src/components/Sidebar/Sidebar.js b/packages/react/src/components/Sidebar/Sidebar.js new file mode 100644 index 000000000..89b165a6b --- /dev/null +++ b/packages/react/src/components/Sidebar/Sidebar.js @@ -0,0 +1,27 @@ +import React from 'react'; +import classes from './Sidebar.module.css'; +import { Box } from '../Box'; +import { Icon } from '../Icon'; +import { ActionButton } from '../ActionButton'; + +const Sidebar = ({ title, iconName, setShowWindow, children }) => ( + + + + + + {title} + setShowWindow(false)} ghost size="small"> + + + + + {children} + + +); +export default Sidebar; diff --git a/packages/react/src/components/Sidebar/Sidebar.module.css b/packages/react/src/components/Sidebar/Sidebar.module.css new file mode 100644 index 000000000..bff71c45c --- /dev/null +++ b/packages/react/src/components/Sidebar/Sidebar.module.css @@ -0,0 +1,41 @@ +.sidebar { + position: fixed; + right: 0; + top: 0; + width: 350px; + height: 100%; + overflow-x: scroll; + overflow-y: scroll; + background-color: white; + box-shadow: -1px 0px 5px rgb(0 0 0 / 25%); + z-index: 100; +} + +.sidebarContainer { + display: flex; + flex-direction: column; + height: 100%; +} + +.sidebarHeader { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.5rem; +} + +.sidebarTitle { + color: #4a4a4a; + width: 80%; +} + +.sidebarIcon { + size: 1.25rem; + padding: 0 0.5 0.5 0rem; +} + +@media (max-width: 550px) { + .sidebar { + width: 100vw; + } +} diff --git a/packages/react/src/components/UserMentions/UserMentions.js b/packages/react/src/components/UserMentions/UserMentions.js index 1bd655aa6..ab8ca7d70 100644 --- a/packages/react/src/components/UserMentions/UserMentions.js +++ b/packages/react/src/components/UserMentions/UserMentions.js @@ -1,11 +1,9 @@ import React, { useState, useEffect } from 'react'; import { css } from '@emotion/react'; import { isSameDay, format } from 'date-fns'; -import classes from './UserMentions.module.css'; import { Icon } from '../Icon'; import { Box } from '../Box'; import { Attachments } from '../Attachments'; -import { ActionButton } from '../ActionButton'; import { useMessageStore, useUserStore, useMentionsStore } from '../../store'; import { MessageBody } from '../Message/MessageBody'; import { MessageMetrics } from '../Message/MessageMetrics'; @@ -15,6 +13,7 @@ import { MessageDivider } from '../Message/MessageDivider'; import MessageAvatarContainer from '../Message/MessageAvatarContainer'; import MessageBodyContainer from '../Message/MessageBodyContainer'; import MessageHeader from '../Message/MessageHeader'; +import Sidebar from '../Sidebar/Sidebar'; const MessageCss = css` display: flex; @@ -65,115 +64,88 @@ const UserMentions = () => { }, [RCInstance, setMentionedMessages]); return ( - - - - - + + {isLoaded && ( + + {mentionedMessages.length === 0 ? ( + - - Mentions - - - - - - - + + No mentions found + + + ) : ( + mentionedMessages.map((message, index, arr) => { + const newDay = + index === 0 || isMessageNewDay(message, arr[index - 1]); + return ( + + {newDay ? ( + + {format(new Date(message.ts), 'MMMM d, yyyy')} + + ) : null} + + {showAvatar && ( + + )} + + - {isLoaded && ( - - {mentionedMessages.length === 0 ? ( - - - - No mentions found - - - ) : ( - mentionedMessages.map((message, index, arr) => { - const newDay = - index === 0 || isMessageNewDay(message, arr[index - 1]); - return ( - - {newDay ? ( - - {format(new Date(message.ts), 'MMMM d, yyyy')} - - ) : null} - - {showAvatar && ( - + {message.attachments && + message.attachments.length > 0 ? ( + <> + + + > + ) : ( + + )} + + + {!message.t && message.tcount && ( + )} - - - - - {message.attachments && - message.attachments.length > 0 ? ( - <> - - - > - ) : ( - - )} - - - {!message.t && message.tcount && ( - - )} - - - - ); - }) - )} - - )} - - + + + + ); + }) + )} + + )} + ); }; diff --git a/packages/react/src/components/UserMentions/UserMentions.module.css b/packages/react/src/components/UserMentions/UserMentions.module.css deleted file mode 100644 index cca051f27..000000000 --- a/packages/react/src/components/UserMentions/UserMentions.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.component { - position: fixed; - right: 0; - top: 0; - width: 350px; - height: 100%; - overflow: hidden; - background-color: white; - box-shadow: -1px 0px 5px rgb(0 0 0 / 25%); - z-index: 100; -} -.wrapContainer { - height: 100%; - display: flex; - flex-direction: column; -} - -@media (max-width: 550px) { - .component { - width: 100vw; - } -} From 7e389e130fdb5c577bf2623ecdebfd15bd1399dd Mon Sep 17 00:00:00 2001 From: Zishan Ahmad Date: Sun, 17 Mar 2024 17:39:11 +0530 Subject: [PATCH 2/2] fix/feat: Added fallback icon and menu for downloading and deleting files (#516) * added fallback icon for avatars * added option to download and delete file fixed styleOverirdes in MessageBodyContainer fixed stylings * removed unused props --- .../react/src/components/Avatar/Avatar.js | 37 ++- .../components/Files/FilePreviewContainer.js | 2 +- packages/react/src/components/Files/Files.js | 299 ++++++++++++------ .../src/components/Icon/icons/Attachment.js | 18 ++ .../components/Icon/icons/CircleArrowDown.js | 14 + .../react/src/components/Icon/icons/index.js | 4 + .../Message/MessageBodyContainer.js | 7 +- packages/react/tools/icons-generator.js | 1 + 8 files changed, 277 insertions(+), 105 deletions(-) create mode 100644 packages/react/src/components/Icon/icons/Attachment.js create mode 100644 packages/react/src/components/Icon/icons/CircleArrowDown.js diff --git a/packages/react/src/components/Avatar/Avatar.js b/packages/react/src/components/Avatar/Avatar.js index 201dfd9ce..95c296a91 100644 --- a/packages/react/src/components/Avatar/Avatar.js +++ b/packages/react/src/components/Avatar/Avatar.js @@ -1,20 +1,36 @@ import { css } from '@emotion/react'; -import React from 'react'; +import React, { useState } from 'react'; import useComponentOverrides from '../../theme/useComponentOverrides'; import { AvatarContainer } from './AvatarContainer'; +import { Icon } from '../Icon'; +import { Box } from '../Box'; export const Avatar = ({ size = '2.25rem', className = '', style = {}, url, + fallbackIcon = 'circle-cross', ...props }) => { + const [imgError, setImgError] = useState(false); const AvatarCss = css` border-radius: 0.25rem; height: ${size}; width: ${size}; `; + + const FallBackBoxCss = css` + display: flex; + justify-content: center; + align-items: center; + background-color: #007fff; + color: #ffffff; + border-radius: 0.25rem; + height: ${size}; + width: ${size}; + `; + const { classNames, styleOverrides } = useComponentOverrides( 'Avatar', className, @@ -23,12 +39,19 @@ export const Avatar = ({ return ( - + {!imgError ? ( + setImgError(true)} + /> + ) : ( + + + + )} ); }; diff --git a/packages/react/src/components/Files/FilePreviewContainer.js b/packages/react/src/components/Files/FilePreviewContainer.js index 55980dc66..819e2cffa 100644 --- a/packages/react/src/components/Files/FilePreviewContainer.js +++ b/packages/react/src/components/Files/FilePreviewContainer.js @@ -16,7 +16,7 @@ const FilePreviewContainer = ({ file, sequential, isStarred }) => { return ( {!sequential ? ( - + ) : isStarred ? ( ) : null} diff --git a/packages/react/src/components/Files/Files.js b/packages/react/src/components/Files/Files.js index f3f830e44..ef3bee20f 100644 --- a/packages/react/src/components/Files/Files.js +++ b/packages/react/src/components/Files/Files.js @@ -1,15 +1,19 @@ -import React, { useState, useMemo, useEffect } from 'react'; +import React, { useState, useMemo, useEffect, useCallback } from 'react'; import { css } from '@emotion/react'; import { Icon } from '../Icon'; import { Box } from '../Box'; import { ActionButton } from '../ActionButton'; -import { useChannelStore, useFileStore } from '../../store'; +import { useChannelStore, useFileStore, useMessageStore } from '../../store'; import { useRCContext } from '../../context/RCInstance'; import { MessageBody } from '../Message/MessageBody'; import MessageBodyContainer from '../Message/MessageBodyContainer'; import FilePreviewContainer from './FilePreviewContainer'; import FilePreviewHeader from './FilePreviewHeader'; import { FileMetrics } from './FileMetrics'; +import Menu from '../Menu/Menu'; +import { Modal } from '../Modal'; +import { Button } from '../Button'; +import { useToastBarDispatch } from '../../hooks/useToastBarDispatch'; const MessageCss = css` display: flex; @@ -84,12 +88,16 @@ const FilePreviewUsernameCss = css` const Files = () => { const { RCInstance } = useRCContext(); + const dispatchToastMessage = useToastBarDispatch(); + const setShowAllFiles = useFileStore((state) => state.setShowAllFiles); const isChannelPrivate = useChannelStore((state) => state.isChannelPrivate); + const messages = useMessageStore((state) => state.messages); const [text, setText] = useState(''); const [isFilesFetched, setIsFilesFetched] = useState(false); const [files, setFiles] = useState([]); + const [fileToDelete, setFileToDelete] = useState({}); const toggleShowAllFiles = () => { setShowAllFiles(false); @@ -107,6 +115,43 @@ const Files = () => { [files, text] ); + const downloadFile = useCallback((url, title) => { + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = title; + + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + }, []); + + const deleteFile = useCallback( + async (file) => { + messages.forEach(async (message) => { + if (message.file?._id === file._id) { + const res = await RCInstance.deleteMessage(message._id); + setFileToDelete({}); + if (res.success) { + dispatchToastMessage({ + type: 'success', + message: 'File deleted', + }); + } else { + dispatchToastMessage({ + type: 'error', + message: 'Error in deleting file', + }); + } + } + }); + }, + [messages, RCInstance, dispatchToastMessage] + ); + + const handleOnClose = () => { + setFileToDelete({}); + }; + useEffect(() => { const fetchAllFiles = async () => { const res = await RCInstance.getAllFiles(isChannelPrivate); @@ -119,108 +164,172 @@ const Files = () => { } }; fetchAllFiles(); - }, [RCInstance, isChannelPrivate, setFiles, setIsFilesFetched]); + }, [RCInstance, isChannelPrivate, setFiles, setIsFilesFetched, fileToDelete]); return ( - - - - - + <> + {fileToDelete && + typeof fileToDelete === 'object' && + Object.keys(fileToDelete).length > 0 && ( + + + + + Are you sure? + + + + + Deleting a file will delete it forever. This cannot be undone. + + + + Cancel + + { + deleteFile(fileToDelete); + }} + > + Delete + + + + )} + + + + + + + + Files + + + + + + + + + + - - Files - - - - - + - - - - - - + {isFilesFetched && ( + + {filteredFiles.length === 0 ? ( + + + + No files found + + + ) : ( + filteredFiles.map( + (file) => + file.path && ( + + + + + + + @{file.user.username} + + + + - {isFilesFetched && ( - - {filteredFiles.length === 0 ? ( - - - - No files found - - - ) : ( - filteredFiles.map( - (file) => - file.path && ( - - - - - - - @{file.user.username} - - - - - - ) - ) - )} - - )} + downloadFile(file.url, file.title), + label: 'Download', + icon: 'circle-arrow-down', + }, + { + id: 'delete', + action: () => setFileToDelete(file), + label: 'Delete', + icon: 'trash', + }, + ]} + /> + + ) + ) + )} + + )} + - + > ); }; diff --git a/packages/react/src/components/Icon/icons/Attachment.js b/packages/react/src/components/Icon/icons/Attachment.js new file mode 100644 index 000000000..18b93a3b4 --- /dev/null +++ b/packages/react/src/components/Icon/icons/Attachment.js @@ -0,0 +1,18 @@ +import React from 'react'; + +const Attachment = (props) => ( + + + +); + +export default Attachment; diff --git a/packages/react/src/components/Icon/icons/CircleArrowDown.js b/packages/react/src/components/Icon/icons/CircleArrowDown.js new file mode 100644 index 000000000..9bae86466 --- /dev/null +++ b/packages/react/src/components/Icon/icons/CircleArrowDown.js @@ -0,0 +1,14 @@ +import React from 'react'; + +const CircleArrowDown = (props) => ( + + + +); + +export default CircleArrowDown; diff --git a/packages/react/src/components/Icon/icons/index.js b/packages/react/src/components/Icon/icons/index.js index 198064cb0..091ebe67e 100644 --- a/packages/react/src/components/Icon/icons/index.js +++ b/packages/react/src/components/Icon/icons/index.js @@ -46,6 +46,8 @@ import At from './At'; import ChevronDown from './ChevronDown'; import ChevronLeft from './ChevronLeft'; import Key from './Key'; +import Attachment from './Attachment'; +import CircleArrowDown from './CircleArrowDown'; const icons = { file: File, @@ -96,6 +98,8 @@ const icons = { 'chevron-down': ChevronDown, 'chevron-left': ChevronLeft, key: Key, + attachment: Attachment, + 'circle-arrow-down': CircleArrowDown, }; export default icons; diff --git a/packages/react/src/components/Message/MessageBodyContainer.js b/packages/react/src/components/Message/MessageBodyContainer.js index cbf24b2e4..724c52042 100644 --- a/packages/react/src/components/Message/MessageBodyContainer.js +++ b/packages/react/src/components/Message/MessageBodyContainer.js @@ -4,10 +4,13 @@ import useComponentOverrides from '../../theme/useComponentOverrides'; import { Box } from '../Box'; import { appendClassNames } from '../../lib/appendClassNames'; -const MessageBodyContainer = ({ children }) => { +const MessageBodyContainer = ({ children, className = '', style = {} }) => { const { classNames, styleOverrides } = useComponentOverrides( - 'MessageBodyContainer' + 'MessageBodyContainer', + className, + style ); + const classNameMBC = css` margin-left: 5px; position: relative; diff --git a/packages/react/tools/icons-generator.js b/packages/react/tools/icons-generator.js index 710904c97..b0ee9b6b7 100644 --- a/packages/react/tools/icons-generator.js +++ b/packages/react/tools/icons-generator.js @@ -42,6 +42,7 @@ const iconsList = [ 'chevron-down', 'chevron-left', 'key', + 'attachment', ]; const svgDirPath = path.join( __dirname,