From a1541e09c631116f5917d99f7f954f7798787ba0 Mon Sep 17 00:00:00 2001 From: Anirban Singha Date: Mon, 23 Dec 2024 22:24:13 +0530 Subject: [PATCH 1/4] feat: add file type filtering dropdown to improve file navigation --- packages/api/src/EmbeddedChatApi.ts | 4 +-- .../views/MessageAggregators/FileGallery.js | 30 +++++++++++++++++++ .../common/MessageAggregator.js | 2 ++ .../src/components/Sidebar/Sidebar.js | 5 +++- .../src/components/Sidebar/SidebarContent.js | 23 +++++++++++++- .../components/StaticSelect/StaticSelect.js | 12 +++++++- .../StaticSelect/StaticSelect.styles.js | 9 ++++++ 7 files changed, 80 insertions(+), 5 deletions(-) diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index 2f843a1cd..fbd570b88 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -732,12 +732,12 @@ export default class EmbeddedChatApi { } } - async getAllFiles(isChannelPrivate = false) { + async getAllFiles(isChannelPrivate = false, typeGroup = "") { const roomType = isChannelPrivate ? "groups" : "channels"; try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch( - `${this.host}/api/v1/${roomType}.files?roomId=${this.rid}`, + `${this.host}/api/v1/${roomType}.files?roomId=${this.rid}&typeGroup=${typeGroup}`, { headers: { "Content-Type": "application/json", diff --git a/packages/react/src/views/MessageAggregators/FileGallery.js b/packages/react/src/views/MessageAggregators/FileGallery.js index 606add5f0..34120a32f 100644 --- a/packages/react/src/views/MessageAggregators/FileGallery.js +++ b/packages/react/src/views/MessageAggregators/FileGallery.js @@ -16,6 +16,15 @@ const FileGallery = () => { const [isFetching, setIsFetching] = useState(true); const [files, setFiles] = useState([]); + const options = [ + { value: 'all', label: 'All' }, + { value: 'application', label: 'Files' }, + { value: 'video', label: 'Videos' }, + { value: 'image', label: 'Images' }, + { value: 'audio', label: 'Audios' }, + { value: 'text', label: 'Texts' }, + ]; + const handleInputChange = (e) => { setText(e.target.value); }; @@ -42,11 +51,32 @@ const FileGallery = () => { fetchAllFiles(); }, [RCInstance, isChannelPrivate, messages]); + const handleFilterSelect = async (val) => { + setIsFetching(true); + let res; + val === 'all' + ? (res = await RCInstance.getAllFiles(isChannelPrivate)) + : (res = await RCInstance.getAllFiles(isChannelPrivate, val)); + if (res?.files) { + const sortedFiles = res.files.sort( + (a, b) => new Date(b.uploadedAt) - new Date(a.uploadedAt) + ); + setFiles(sortedFiles); + setIsFetching(false); + } + }; + return ( setExclusiveState(null)} style={{ diff --git a/packages/ui-elements/src/components/Sidebar/Sidebar.js b/packages/ui-elements/src/components/Sidebar/Sidebar.js index 8c8ec7092..cd9e7a1f7 100644 --- a/packages/ui-elements/src/components/Sidebar/Sidebar.js +++ b/packages/ui-elements/src/components/Sidebar/Sidebar.js @@ -11,6 +11,7 @@ const Sidebar = ({ iconName, onClose, children, + filterProps = {}, searchProps = {}, footer, style = {}, @@ -26,7 +27,9 @@ const Sidebar = ({ style={{ ...style, ...styleOverrides }} > - {children} + + {children} + {footer && {footer}} ); diff --git a/packages/ui-elements/src/components/Sidebar/SidebarContent.js b/packages/ui-elements/src/components/Sidebar/SidebarContent.js index 7e8756f87..18df17dc2 100644 --- a/packages/ui-elements/src/components/Sidebar/SidebarContent.js +++ b/packages/ui-elements/src/components/Sidebar/SidebarContent.js @@ -4,13 +4,20 @@ import { Icon } from '../Icon'; import { Input } from '../Input'; import { getSidebarContentStyles } from './Sidebar.styles'; import { useTheme } from '../../hooks'; +import { StaticSelect } from '../StaticSelect'; -const SidebarContent = ({ children, searchProps = {}, style }) => { +const SidebarContent = ({ + children, + searchProps = {}, + style, + filterProps = {}, +}) => { const { isSearch = false, handleInputChange, placeholder, } = searchProps || {}; + const { isFile, options, value, handleFilterSelect } = filterProps || {}; const searchContainerRef = useRef(null); const { theme } = useTheme(); const styles = getSidebarContentStyles(theme); @@ -48,6 +55,20 @@ const SidebarContent = ({ children, searchProps = {}, style }) => { )} + {isFile && ( + + + + )} {children} ); diff --git a/packages/ui-elements/src/components/StaticSelect/StaticSelect.js b/packages/ui-elements/src/components/StaticSelect/StaticSelect.js index d3123dfba..2757c4b91 100644 --- a/packages/ui-elements/src/components/StaticSelect/StaticSelect.js +++ b/packages/ui-elements/src/components/StaticSelect/StaticSelect.js @@ -12,6 +12,7 @@ const StaticSelect = ({ style = {}, options = [], placeholder = '', + isFile, value, onSelect, disabled = false, @@ -85,13 +86,22 @@ const StaticSelect = ({ - {isOpen && ( + {isOpen && !isFile && ( )} + {isOpen && isFile && ( + + + + )} ); }; diff --git a/packages/ui-elements/src/components/StaticSelect/StaticSelect.styles.js b/packages/ui-elements/src/components/StaticSelect/StaticSelect.styles.js index 3374417f6..0e9f07c2e 100644 --- a/packages/ui-elements/src/components/StaticSelect/StaticSelect.styles.js +++ b/packages/ui-elements/src/components/StaticSelect/StaticSelect.styles.js @@ -38,6 +38,15 @@ const getStaticSelectStyles = (theme) => { cursor: not-allowed !important; color: ${theme.colors.mutedForeground}; `, + + fileTypeSelect: css` + position: absolute; + z-index: 1000; + top: 100%; + left: 0; + width: 100%; + background-color: white; + `, }; return styles; From 5549da89c6ab194b517ef5193dc945d27f1f32a0 Mon Sep 17 00:00:00 2001 From: Anirban Singha <143536290+SinghaAnirban005@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:18:56 +0530 Subject: [PATCH 2/4] Update StaticSelect.styles.js --- .../src/components/StaticSelect/StaticSelect.styles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-elements/src/components/StaticSelect/StaticSelect.styles.js b/packages/ui-elements/src/components/StaticSelect/StaticSelect.styles.js index 0e9f07c2e..e42215564 100644 --- a/packages/ui-elements/src/components/StaticSelect/StaticSelect.styles.js +++ b/packages/ui-elements/src/components/StaticSelect/StaticSelect.styles.js @@ -41,7 +41,7 @@ const getStaticSelectStyles = (theme) => { fileTypeSelect: css` position: absolute; - z-index: 1000; + z-index: 10; top: 100%; left: 0; width: 100%; From 24147ee4ab3bd7e46cf12ebd7cfa9a16d6cd7a3b Mon Sep 17 00:00:00 2001 From: Anirban Singha Date: Sat, 4 Jan 2025 02:02:41 +0530 Subject: [PATCH 3/4] change layout of files search sidebar from col to row --- packages/api/src/EmbeddedChatApi.ts | 25 ++++---- .../views/MessageAggregators/FileGallery.js | 4 +- .../src/components/Sidebar/Sidebar.styles.js | 7 ++- .../src/components/Sidebar/SidebarContent.js | 63 ++++++++++--------- 4 files changed, 54 insertions(+), 45 deletions(-) diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index fbd570b88..3a1c3b5f9 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -732,21 +732,22 @@ export default class EmbeddedChatApi { } } - async getAllFiles(isChannelPrivate = false, typeGroup = "") { + async getAllFiles(isChannelPrivate = false, typeGroup: string) { const roomType = isChannelPrivate ? "groups" : "channels"; try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; - const response = await fetch( - `${this.host}/api/v1/${roomType}.files?roomId=${this.rid}&typeGroup=${typeGroup}`, - { - headers: { - "Content-Type": "application/json", - "X-Auth-Token": authToken, - "X-User-Id": userId, - }, - method: "GET", - } - ); + const url = + typeGroup === "" + ? `${this.host}/api/v1/${roomType}.files?roomId=${this.rid}` + : `${this.host}/api/v1/${roomType}.files?roomId=${this.rid}&typeGroup=${typeGroup}`; + const response = await fetch(url, { + headers: { + "Content-Type": "application/json", + "X-Auth-Token": authToken, + "X-User-Id": userId, + }, + method: "GET", + }); return await response.json(); } catch (err) { console.error(err); diff --git a/packages/react/src/views/MessageAggregators/FileGallery.js b/packages/react/src/views/MessageAggregators/FileGallery.js index 34120a32f..69b0e859a 100644 --- a/packages/react/src/views/MessageAggregators/FileGallery.js +++ b/packages/react/src/views/MessageAggregators/FileGallery.js @@ -39,7 +39,7 @@ const FileGallery = () => { useEffect(() => { const fetchAllFiles = async () => { - const res = await RCInstance.getAllFiles(isChannelPrivate); + const res = await RCInstance.getAllFiles(isChannelPrivate, ''); if (res?.files) { const sortedFiles = res.files.sort( (a, b) => new Date(b.uploadedAt) - new Date(a.uploadedAt) @@ -55,7 +55,7 @@ const FileGallery = () => { setIsFetching(true); let res; val === 'all' - ? (res = await RCInstance.getAllFiles(isChannelPrivate)) + ? (res = await RCInstance.getAllFiles(isChannelPrivate, '')) : (res = await RCInstance.getAllFiles(isChannelPrivate, val)); if (res?.files) { const sortedFiles = res.files.sort( diff --git a/packages/ui-elements/src/components/Sidebar/Sidebar.styles.js b/packages/ui-elements/src/components/Sidebar/Sidebar.styles.js index bf7d07ef7..bc8fe1c7b 100644 --- a/packages/ui-elements/src/components/Sidebar/Sidebar.styles.js +++ b/packages/ui-elements/src/components/Sidebar/Sidebar.styles.js @@ -34,11 +34,16 @@ export const getSidebarContentStyles = (theme) => { padding: 0 0.5rem; border-radius: ${theme.radius}; position: relative; - margin: 0 1rem 1rem; &.focused { outline: 1px solid ${theme.colors.ring}; } `, + filesHeader: css` + display: flex; + align-items: center; + justify-content: space-between; + margin: 1px 1rem 0; + `, textInput: css` border: none; diff --git a/packages/ui-elements/src/components/Sidebar/SidebarContent.js b/packages/ui-elements/src/components/Sidebar/SidebarContent.js index 18df17dc2..cbe19088b 100644 --- a/packages/ui-elements/src/components/Sidebar/SidebarContent.js +++ b/packages/ui-elements/src/components/Sidebar/SidebarContent.js @@ -36,39 +36,42 @@ const SidebarContent = ({ return ( - {isSearch && ( - - - - - )} - {isFile && ( - - + {isSearch && ( + - - )} + ref={searchContainerRef} + > + + + + )} + {isFile && ( + + + + )} + {children} ); From 457569eb22235d1a47719eabc9aedde8cca96cc3 Mon Sep 17 00:00:00 2001 From: Anirban Singha Date: Sat, 11 Jan 2025 21:36:53 +0530 Subject: [PATCH 4/4] fix: ensure selected value is persistent in placeholder --- .../views/MessageAggregators/FileGallery.js | 4 +++- .../components/StaticSelect/StaticSelect.js | 23 ++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/react/src/views/MessageAggregators/FileGallery.js b/packages/react/src/views/MessageAggregators/FileGallery.js index 69b0e859a..8c23f6eef 100644 --- a/packages/react/src/views/MessageAggregators/FileGallery.js +++ b/packages/react/src/views/MessageAggregators/FileGallery.js @@ -15,6 +15,7 @@ const FileGallery = () => { const [text, setText] = useState(''); const [isFetching, setIsFetching] = useState(true); const [files, setFiles] = useState([]); + const [selectedFilter, setSelectedFilter] = useState('all'); const options = [ { value: 'all', label: 'All' }, @@ -53,6 +54,7 @@ const FileGallery = () => { const handleFilterSelect = async (val) => { setIsFetching(true); + setSelectedFilter(val); let res; val === 'all' ? (res = await RCInstance.getAllFiles(isChannelPrivate, '')) @@ -74,7 +76,7 @@ const FileGallery = () => { filterProps={{ isFile: true, options, - value: 'all', + value: selectedFilter, handleFilterSelect, }} searchProps={{ diff --git a/packages/ui-elements/src/components/StaticSelect/StaticSelect.js b/packages/ui-elements/src/components/StaticSelect/StaticSelect.js index 2757c4b91..b64e40b0f 100644 --- a/packages/ui-elements/src/components/StaticSelect/StaticSelect.js +++ b/packages/ui-elements/src/components/StaticSelect/StaticSelect.js @@ -23,9 +23,18 @@ const StaticSelect = ({ const styles = getStaticSelectStyles(theme); const [isOpen, setIsOpen] = useState(false); - const [internalValue, setInternalValue] = useState(''); + const [internalValue, setInternalValue] = useState(value || ''); + const [selectedOption, setSelectedOption] = useState(null); const staticSelectRef = useRef(null); + useEffect(() => { + setInternalValue(value || ''); + const option = options.find((opt) => opt.value === value); + if (option) { + setSelectedOption(option); + } + }, [value, options]); + const toggleDropdown = () => { if (!disabled) { setIsOpen(!isOpen); @@ -33,17 +42,15 @@ const StaticSelect = ({ }; const handleSelect = (optionValue) => { + const selectedOpt = options.find((opt) => opt.value === optionValue); setInternalValue(optionValue); + setSelectedOption(selectedOpt); setIsOpen(false); if (onSelect) { onSelect(optionValue); } }; - useEffect(() => { - setInternalValue(value || ''); - }, [value]); - useEffect(() => { const handleClickOutside = (event) => { if ( @@ -62,6 +69,8 @@ const StaticSelect = ({ }; }, [isOpen]); + const displayValue = selectedOption?.label || placeholder; + return ( - {!isOpen && internalValue - ? options.find((option) => option.value === internalValue)?.label - : placeholder} + {displayValue}