From 52be328ad65ffcd86b86bc730072af8e2bc4040c Mon Sep 17 00:00:00 2001 From: Hugo Carreira Date: Wed, 12 Jun 2024 17:36:05 +0100 Subject: [PATCH 1/6] Created thread filter input --- src/components/ChatList/ChatHistoryDrawer.tsx | 249 +++++++++++------- .../ChatList/ChatHistoryThreadItem.tsx | 96 +++++++ 2 files changed, 245 insertions(+), 100 deletions(-) create mode 100644 src/components/ChatList/ChatHistoryThreadItem.tsx diff --git a/src/components/ChatList/ChatHistoryDrawer.tsx b/src/components/ChatList/ChatHistoryDrawer.tsx index 386a13b..de49662 100644 --- a/src/components/ChatList/ChatHistoryDrawer.tsx +++ b/src/components/ChatList/ChatHistoryDrawer.tsx @@ -1,8 +1,6 @@ -import { Delete, MoreHoriz } from "@mui/icons-material"; import AddIcon from "@mui/icons-material/Add"; import Brightness4Icon from "@mui/icons-material/Brightness4"; import Brightness7Icon from "@mui/icons-material/Brightness7"; -import DriveFileRenameIcon from "@mui/icons-material/DriveFileRenameOutlineSharp"; import LogoutIcon from "@mui/icons-material/Logout"; import MenuIcon from "@mui/icons-material/Menu"; import { @@ -12,11 +10,10 @@ import { Drawer, Fab, ListItemButton, - Menu, - MenuItem, Skeleton, - styled, TextField, + Tooltip, + styled, } from "@mui/material"; import AppBar from "@mui/material/AppBar"; import Box from "@mui/material/Box"; @@ -28,14 +25,24 @@ import ListItem from "@mui/material/ListItem"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; import Toolbar from "@mui/material/Toolbar"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useAuthContext } from "../../context/AuthContext"; import { useSettings } from "../../context/SettingsContext"; -import { Thread, useThreadContext } from "../../context/ThreadContext"; +import { useThreadContext } from "../../context/ThreadContext"; import ChatWindow from "../ChatWindow/ChatWindow"; +import { ThreadItem } from "./ChatHistoryThreadItem"; const drawerWidth = 300; +enum LogicalOperator { + AND = "AND", + OR = "OR" +}; +type LogicalOperatorT = keyof typeof LogicalOperator; +const logicalOperators = Object.values(LogicalOperator) as LogicalOperatorT[]; + +const NEW_THREAD_SEARCH_CONTENT = ""; + const Error = styled("div")(({ theme }) => ({ color: theme.palette.error.main, backgroundColor: theme.palette.background.default, @@ -49,98 +56,116 @@ const Error = styled("div")(({ theme }) => ({ flexDirection: "column", })); -const RenameText = styled(TextField)(({ theme }) => ({ - paddingBlock: theme.spacing(1), - paddingInline: theme.spacing(2), -})); - -const ThreadItem = ({ thread }: { thread: Thread }) => { - const [renaming, setRenaming] = useState(false); - const [newTitle, setNewTitle] = useState(thread.title || "New Chat"); - const { setSelectedThread, selectedThreadId, deleteThread, renameThread } = - useThreadContext(); - const inputRef = useRef(null); - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - const handleClose = () => { - setAnchorEl(null); - }; - - return ( - - - - } - > - {renaming ? ( - setNewTitle(event.target.value)} - inputRef={inputRef} - onBlur={() => { - setRenaming(false); - renameThread(thread.id, newTitle, thread.newThread); - }} - onKeyDown={(ev) => { - if (ev.key === "Enter") { - (ev.target as HTMLElement).blur(); - } - }} - /> - ) : ( - setSelectedThread(thread.id)} - selected={selectedThreadId === thread.id} - > - - - )} - handleClose()} open={open}> - { - deleteThread(thread.id, thread.newThread); - handleClose(); - }} - > - - Delete - - { - setRenaming(true); - setTimeout(() => { - inputRef.current?.focus(); - inputRef.current?.select(); - }); - handleClose(); - }} - > - - Rename - - - - ); -}; - const ChatHistoryDrawer = () => { const { logout, profile } = useAuthContext(); + const { darkMode, toggleDarkMode } = useSettings(); const { threads, listThreads, createThread, loading, error } = useThreadContext(); - const { darkMode, toggleDarkMode } = useSettings(); + // lower case thread data to be searched and filtered (including a stub for the "New Chat" thread) + const [searchableThreads, setSearchableThreads] = useState(); + // result of the filtering + const [filteredThreadsIndexes, setFilteredThreadsIndexes] = useState([]); + // search text input (split, if a logical operator was used) + const [searchFilter, setSearchFilter] = useState([]); + // AND or OR or undefined + const [searchLogicalOperator, setSearchLogicalOperator] = useState(); useEffect(() => { listThreads(); }, [listThreads]); + // groups and transforms to lower case the messages from each thread + useEffect(() => { + if (threads.length > 0) { + const newSearchableThreads: string[][] = []; + for (let i = 0; i < threads.length; i++) { + const threadMessagesContent: string[] = []; + if (threads[i].newThread) { + newSearchableThreads.push([NEW_THREAD_SEARCH_CONTENT]); + } else { + for (const msg of threads[i].messages) { + threadMessagesContent.push(msg.data.content.toLocaleLowerCase()); + } + newSearchableThreads.push( + threadMessagesContent + ); + } + } + setSearchableThreads(newSearchableThreads); + } + }, [threads, setSearchableThreads]); + + useEffect(() => { + if (threads.length > 0) { + if ((!searchFilter || searchFilter.length === 0) && threads.length !== filteredThreadsIndexes.length) { + setFilteredThreadsIndexes(threads.map((it, idx) => idx)); + } else if (searchableThreads) { + const newFilteredThreadsIndexes: number[] = []; + const NEW_THREAD_INDEX = threads.findIndex(it => it.newThread); + searchableThreads.forEach((searchableThread, idx) => { + if (idx === NEW_THREAD_INDEX && searchableThreads.length > 0 && searchableThreads[NEW_THREAD_INDEX].length === 1 && searchableThreads[NEW_THREAD_INDEX][0] === NEW_THREAD_SEARCH_CONTENT) { + newFilteredThreadsIndexes.push(NEW_THREAD_INDEX); + } else { + let isIncluded = false; + const andPartsFoundOnThread: boolean[] = Array(searchFilter.length).fill(false); + let searchFilterPart; + for (let i = 0; i < searchFilter.length; i++) { + searchFilterPart = searchFilter[i]; + if (isIncluded || andPartsFoundOnThread.every((it) => it)) { + break; + } + for (const searchableThreadPart of searchableThread) { + if (searchableThreadPart.includes(searchFilterPart)) { + if (!searchLogicalOperator || searchLogicalOperator === LogicalOperator.OR || searchFilter.length === 1) { + isIncluded = true; + } else { + andPartsFoundOnThread[i] = true; + } + break; + } + } + } + if (isIncluded || andPartsFoundOnThread.every((it) => it)) { + newFilteredThreadsIndexes.push(idx); + } + } + }); + setFilteredThreadsIndexes(newFilteredThreadsIndexes); + } + } + }, [searchFilter, searchLogicalOperator, searchableThreads, setFilteredThreadsIndexes, threads]); + + const handleSearchOnChange = useCallback((event: { target: { value: string; }; }) => { + const { value } = event.target; + const trimmedValue = value.trim(); + if (trimmedValue.length > 0) { + let logicalOperatorOnSearch; + for (const logicalOperator of logicalOperators) { + if (value.includes(` ${logicalOperator} `)) { + logicalOperatorOnSearch = logicalOperator; + break; + } + } + const splitPhrases = (logicalOperatorOnSearch) + ? value + .split(` ${logicalOperatorOnSearch} `) + .map(it => it.trim().toLocaleLowerCase()) + .filter(it => it !== "") + : [trimmedValue]; + setSearchFilter(splitPhrases); + if (searchLogicalOperator !== logicalOperatorOnSearch) { + setSearchLogicalOperator(logicalOperatorOnSearch); + } + } else { + if (searchFilter.length > 0) { + setSearchFilter([]); + } + if (searchLogicalOperator) { + setSearchLogicalOperator(undefined); + } + } + }, [setSearchFilter, searchFilter, searchLogicalOperator, setSearchLogicalOperator]); + return ( @@ -178,16 +203,40 @@ const ChatHistoryDrawer = () => { ) : ( - - {[...Array(loading ? 4 : 0)].map((_, index) => ( - - - - ))} - {threads.map((thread) => ( - - ))} - + <> + + + + + + + {[...Array(loading ? 4 : 0)].map((_, index) => ( + + + + ))} + {threads.filter((it, idx) => (searchFilter.length === 0 + || filteredThreadsIndexes.includes(idx) + )).map((thread) => ( + + ))} + + + )} diff --git a/src/components/ChatList/ChatHistoryThreadItem.tsx b/src/components/ChatList/ChatHistoryThreadItem.tsx new file mode 100644 index 0000000..11c42c5 --- /dev/null +++ b/src/components/ChatList/ChatHistoryThreadItem.tsx @@ -0,0 +1,96 @@ +import { Delete, MoreHoriz } from "@mui/icons-material"; +import DriveFileRenameIcon from "@mui/icons-material/DriveFileRenameOutlineSharp"; +import { + ListItemButton, + Menu, + MenuItem, + styled, + TextField, +} from "@mui/material"; +import IconButton from "@mui/material/IconButton"; +import ListItem from "@mui/material/ListItem"; +import ListItemText from "@mui/material/ListItemText"; +import { Thread, useThreadContext } from "../../context/ThreadContext"; +import { useRef, useState } from 'react'; + +const RenameText = styled(TextField)(({ theme }) => ({ + paddingBlock: theme.spacing(1), + paddingInline: theme.spacing(2), +})); + +export const ThreadItem = ({ thread }: { thread: Thread }) => { + const [renaming, setRenaming] = useState(false); + const [newTitle, setNewTitle] = useState(thread.title || "New Chat"); + const { setSelectedThread, selectedThreadId, deleteThread, renameThread } = + useThreadContext(); + const inputRef = useRef(null); + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + + + + } + > + {renaming ? ( + setNewTitle(event.target.value)} + inputRef={inputRef} + onBlur={() => { + setRenaming(false); + renameThread(thread.id, newTitle, thread.newThread); + }} + onKeyDown={(ev) => { + if (ev.key === "Enter") { + (ev.target as HTMLElement).blur(); + } + }} + /> + ) : ( + setSelectedThread(thread.id)} + selected={selectedThreadId === thread.id} + > + + + )} + handleClose()} open={open}> + { + deleteThread(thread.id, thread.newThread); + handleClose(); + }} + > + + Delete + + { + setRenaming(true); + setTimeout(() => { + inputRef.current?.focus(); + inputRef.current?.select(); + }); + handleClose(); + }} + > + + Rename + + + + ); +}; \ No newline at end of file From ed3699c0c74fb70d3de826b223e7e68011b21dbe Mon Sep 17 00:00:00 2001 From: Hugo Carreira Date: Fri, 14 Jun 2024 10:10:07 +0100 Subject: [PATCH 2/6] Added dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b8da390..77be25b 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "eslint-plugin-react": "^7.33.2", "prettier": "^3.1.0", "prettier-plugin-organize-imports": "^3.2.4", + "prop-types": "^15.8.1", "typescript": "^5.3.3", "vite-plugin-eslint": "^1.8.1" } From c0113c6c37936b1356c0a559fbc0a0e4082960f6 Mon Sep 17 00:00:00 2001 From: Hugo Carreira Date: Fri, 14 Jun 2024 10:10:41 +0100 Subject: [PATCH 3/6] Refactored the search input --- src/components/ChatList/ChatHistoryDrawer.tsx | 139 ++------------- src/components/ChatList/ChatSearch.tsx | 168 ++++++++++++++++++ 2 files changed, 178 insertions(+), 129 deletions(-) create mode 100644 src/components/ChatList/ChatSearch.tsx diff --git a/src/components/ChatList/ChatHistoryDrawer.tsx b/src/components/ChatList/ChatHistoryDrawer.tsx index de49662..d352af5 100644 --- a/src/components/ChatList/ChatHistoryDrawer.tsx +++ b/src/components/ChatList/ChatHistoryDrawer.tsx @@ -11,8 +11,6 @@ import { Fab, ListItemButton, Skeleton, - TextField, - Tooltip, styled, } from "@mui/material"; import AppBar from "@mui/material/AppBar"; @@ -25,24 +23,16 @@ import ListItem from "@mui/material/ListItem"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; import Toolbar from "@mui/material/Toolbar"; -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useAuthContext } from "../../context/AuthContext"; import { useSettings } from "../../context/SettingsContext"; import { useThreadContext } from "../../context/ThreadContext"; import ChatWindow from "../ChatWindow/ChatWindow"; import { ThreadItem } from "./ChatHistoryThreadItem"; +import { ChatSearch } from './ChatSearch'; const drawerWidth = 300; -enum LogicalOperator { - AND = "AND", - OR = "OR" -}; -type LogicalOperatorT = keyof typeof LogicalOperator; -const logicalOperators = Object.values(LogicalOperator) as LogicalOperatorT[]; - -const NEW_THREAD_SEARCH_CONTENT = ""; - const Error = styled("div")(({ theme }) => ({ color: theme.palette.error.main, backgroundColor: theme.palette.background.default, @@ -61,111 +51,15 @@ const ChatHistoryDrawer = () => { const { darkMode, toggleDarkMode } = useSettings(); const { threads, listThreads, createThread, loading, error } = useThreadContext(); - // lower case thread data to be searched and filtered (including a stub for the "New Chat" thread) - const [searchableThreads, setSearchableThreads] = useState(); - // result of the filtering + // result of the search input filtering const [filteredThreadsIndexes, setFilteredThreadsIndexes] = useState([]); - // search text input (split, if a logical operator was used) + // search input (split, if a logical operator was used) const [searchFilter, setSearchFilter] = useState([]); - // AND or OR or undefined - const [searchLogicalOperator, setSearchLogicalOperator] = useState(); useEffect(() => { listThreads(); }, [listThreads]); - // groups and transforms to lower case the messages from each thread - useEffect(() => { - if (threads.length > 0) { - const newSearchableThreads: string[][] = []; - for (let i = 0; i < threads.length; i++) { - const threadMessagesContent: string[] = []; - if (threads[i].newThread) { - newSearchableThreads.push([NEW_THREAD_SEARCH_CONTENT]); - } else { - for (const msg of threads[i].messages) { - threadMessagesContent.push(msg.data.content.toLocaleLowerCase()); - } - newSearchableThreads.push( - threadMessagesContent - ); - } - } - setSearchableThreads(newSearchableThreads); - } - }, [threads, setSearchableThreads]); - - useEffect(() => { - if (threads.length > 0) { - if ((!searchFilter || searchFilter.length === 0) && threads.length !== filteredThreadsIndexes.length) { - setFilteredThreadsIndexes(threads.map((it, idx) => idx)); - } else if (searchableThreads) { - const newFilteredThreadsIndexes: number[] = []; - const NEW_THREAD_INDEX = threads.findIndex(it => it.newThread); - searchableThreads.forEach((searchableThread, idx) => { - if (idx === NEW_THREAD_INDEX && searchableThreads.length > 0 && searchableThreads[NEW_THREAD_INDEX].length === 1 && searchableThreads[NEW_THREAD_INDEX][0] === NEW_THREAD_SEARCH_CONTENT) { - newFilteredThreadsIndexes.push(NEW_THREAD_INDEX); - } else { - let isIncluded = false; - const andPartsFoundOnThread: boolean[] = Array(searchFilter.length).fill(false); - let searchFilterPart; - for (let i = 0; i < searchFilter.length; i++) { - searchFilterPart = searchFilter[i]; - if (isIncluded || andPartsFoundOnThread.every((it) => it)) { - break; - } - for (const searchableThreadPart of searchableThread) { - if (searchableThreadPart.includes(searchFilterPart)) { - if (!searchLogicalOperator || searchLogicalOperator === LogicalOperator.OR || searchFilter.length === 1) { - isIncluded = true; - } else { - andPartsFoundOnThread[i] = true; - } - break; - } - } - } - if (isIncluded || andPartsFoundOnThread.every((it) => it)) { - newFilteredThreadsIndexes.push(idx); - } - } - }); - setFilteredThreadsIndexes(newFilteredThreadsIndexes); - } - } - }, [searchFilter, searchLogicalOperator, searchableThreads, setFilteredThreadsIndexes, threads]); - - const handleSearchOnChange = useCallback((event: { target: { value: string; }; }) => { - const { value } = event.target; - const trimmedValue = value.trim(); - if (trimmedValue.length > 0) { - let logicalOperatorOnSearch; - for (const logicalOperator of logicalOperators) { - if (value.includes(` ${logicalOperator} `)) { - logicalOperatorOnSearch = logicalOperator; - break; - } - } - const splitPhrases = (logicalOperatorOnSearch) - ? value - .split(` ${logicalOperatorOnSearch} `) - .map(it => it.trim().toLocaleLowerCase()) - .filter(it => it !== "") - : [trimmedValue]; - setSearchFilter(splitPhrases); - if (searchLogicalOperator !== logicalOperatorOnSearch) { - setSearchLogicalOperator(logicalOperatorOnSearch); - } - } else { - if (searchFilter.length > 0) { - setSearchFilter([]); - } - if (searchLogicalOperator) { - setSearchLogicalOperator(undefined); - } - } - }, [setSearchFilter, searchFilter, searchLogicalOperator, setSearchLogicalOperator]); - return ( @@ -204,25 +98,12 @@ const ChatHistoryDrawer = () => { ) : ( <> - - - - - + {[...Array(loading ? 4 : 0)].map((_, index) => ( diff --git a/src/components/ChatList/ChatSearch.tsx b/src/components/ChatList/ChatSearch.tsx new file mode 100644 index 0000000..b590558 --- /dev/null +++ b/src/components/ChatList/ChatSearch.tsx @@ -0,0 +1,168 @@ +import { + Box, + TextField, + Tooltip, +} from "@mui/material"; +import { useCallback, useEffect, useState } from "react"; +import PropTypes from 'prop-types'; +import { useThreadContext } from "../../context/ThreadContext"; + +enum LogicalOperator { + AND = "AND", + OR = "OR" +}; +type LogicalOperatorT = keyof typeof LogicalOperator; +const logicalOperators = Object.values(LogicalOperator) as LogicalOperatorT[]; + +const NEW_THREAD_SEARCH_CONTENT = ""; + +export const ChatSearch = ({ + setFilteredThreadsIndexes, + setSearchFilter, + searchFilter, + filteredThreadsIndexes +}: { + setFilteredThreadsIndexes: React.Dispatch>; + setSearchFilter: React.Dispatch>; + searchFilter: string[]; + filteredThreadsIndexes: number[]; +}) => { + const { threads, listThreads, loading } = + useThreadContext(); + // lower case thread data to be searched and filtered (including a stub for the "New Chat" thread) + const [searchableThreads, setSearchableThreads] = useState(); + // AND or OR or undefined + const [searchLogicalOperator, setSearchLogicalOperator] = useState(); + + useEffect(() => { + listThreads(); + }, [listThreads]); + + // groups and transforms to lower case the messages from each thread + useEffect(() => { + if (threads.length > 0) { + const newSearchableThreads: string[][] = []; + for (let i = 0; i < threads.length; i++) { + const threadMessagesContent: string[] = []; + if (threads[i].newThread) { + newSearchableThreads.push([NEW_THREAD_SEARCH_CONTENT]); + } else { + for (const msg of threads[i].messages) { + threadMessagesContent.push(msg.data.content.toLocaleLowerCase()); + } + newSearchableThreads.push( + threadMessagesContent + ); + } + } + setSearchableThreads(newSearchableThreads); + } + }, [threads, setSearchableThreads]); + + // filters the threads according to the search input + useEffect(() => { + if (threads.length > 0) { + if ((!searchFilter || searchFilter.length === 0) && threads.length !== filteredThreadsIndexes.length) { + setFilteredThreadsIndexes(threads.map((it, idx) => idx)); + } else if (searchableThreads) { + const newFilteredThreadsIndexes: number[] = []; + const NEW_THREAD_INDEX = threads.findIndex(it => it.newThread); + searchableThreads.forEach((searchableThread, idx) => { + if (idx === NEW_THREAD_INDEX + && searchableThreads.length > 0 + && searchableThreads[NEW_THREAD_INDEX].length === 1 + && searchableThreads[NEW_THREAD_INDEX][0] === NEW_THREAD_SEARCH_CONTENT + ) { + newFilteredThreadsIndexes.push(NEW_THREAD_INDEX); + } else { + // used with the OR and the "no logical operator" scenarios + let isIncluded = false; + // used with the AND scenario + const andPartsFoundOnThread: boolean[] = Array(searchFilter.length).fill(false); + let searchFilterPart; + for (let i = 0; i < searchFilter.length; i++) { + searchFilterPart = searchFilter[i]; + if (isIncluded || andPartsFoundOnThread.every((it) => it)) { + break; + } + for (const searchableThreadPart of searchableThread) { + if (searchableThreadPart.includes(searchFilterPart)) { + if (!searchLogicalOperator || searchLogicalOperator === LogicalOperator.OR || searchFilter.length === 1) { + isIncluded = true; + } else { + andPartsFoundOnThread[i] = true; + } + break; + } + } + } + if (isIncluded || andPartsFoundOnThread.every((it) => it)) { + newFilteredThreadsIndexes.push(idx); + } + } + }); + setFilteredThreadsIndexes(newFilteredThreadsIndexes); + } + } + }, [searchFilter, searchLogicalOperator, searchableThreads, setFilteredThreadsIndexes, threads]); + + const handleSearchOnChange = useCallback((event: { target: { value: string; }; }) => { + const { value } = event.target; + const trimmedValue = value.trim(); + if (trimmedValue.length > 0) { + let logicalOperatorOnSearch; + for (const logicalOperator of logicalOperators) { + if (value.includes(` ${logicalOperator} `)) { + logicalOperatorOnSearch = logicalOperator; + break; + } + } + const splitPhrases = (logicalOperatorOnSearch) + ? value + .split(` ${logicalOperatorOnSearch} `) + .map(it => it.trim().toLocaleLowerCase()) + .filter(it => it !== "") + : [trimmedValue]; + setSearchFilter(splitPhrases); + if (searchLogicalOperator !== logicalOperatorOnSearch) { + setSearchLogicalOperator(logicalOperatorOnSearch); + } + } else { + if (searchFilter.length > 0) { + setSearchFilter([]); + } + if (searchLogicalOperator) { + setSearchLogicalOperator(undefined); + } + } + }, [setSearchFilter, searchFilter, searchLogicalOperator, setSearchLogicalOperator]); + + return ( + + + + + + ); +}; + +ChatSearch.propTypes = { + setFilteredThreadsIndexes: PropTypes.func.isRequired, + setSearchFilter: PropTypes.func.isRequired, + searchFilter: PropTypes.array.isRequired, + filteredThreadsIndexes: PropTypes.array.isRequired +}; From c1d9e96fb8c4f0891789986fb05edac27952c250 Mon Sep 17 00:00:00 2001 From: Hugo Carreira Date: Fri, 14 Jun 2024 11:57:42 +0100 Subject: [PATCH 4/6] Added renamed title to the search --- src/components/ChatList/ChatHistoryDrawer.tsx | 2 +- src/components/ChatList/ChatSearch.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ChatList/ChatHistoryDrawer.tsx b/src/components/ChatList/ChatHistoryDrawer.tsx index d352af5..50e7991 100644 --- a/src/components/ChatList/ChatHistoryDrawer.tsx +++ b/src/components/ChatList/ChatHistoryDrawer.tsx @@ -110,7 +110,7 @@ const ChatHistoryDrawer = () => { ))} - {threads.filter((it, idx) => (searchFilter.length === 0 + {threads.filter((_it, idx) => (searchFilter.length === 0 || filteredThreadsIndexes.includes(idx) )).map((thread) => ( diff --git a/src/components/ChatList/ChatSearch.tsx b/src/components/ChatList/ChatSearch.tsx index b590558..9e1bc03 100644 --- a/src/components/ChatList/ChatSearch.tsx +++ b/src/components/ChatList/ChatSearch.tsx @@ -47,6 +47,10 @@ export const ChatSearch = ({ if (threads[i].newThread) { newSearchableThreads.push([NEW_THREAD_SEARCH_CONTENT]); } else { + if (threads[i].title) { + // @ts-ignore + threadMessagesContent.push(threads[i].title.toLocaleLowerCase()); + } for (const msg of threads[i].messages) { threadMessagesContent.push(msg.data.content.toLocaleLowerCase()); } From b2732a56a587af2f4679761e2c2776aa3fdc0143 Mon Sep 17 00:00:00 2001 From: Hugo Carreira Date: Fri, 14 Jun 2024 12:43:17 +0100 Subject: [PATCH 5/6] Refactoring --- src/components/ChatList/ChatHistoryDrawer.tsx | 57 +++---------------- src/components/ChatList/ChatHistoryFooter.tsx | 41 +++++++++++++ ...storyThreadItem.tsx => ChatThreadItem.tsx} | 0 src/components/ChatList/ChatThreadList.tsx | 37 ++++++++++++ 4 files changed, 87 insertions(+), 48 deletions(-) create mode 100644 src/components/ChatList/ChatHistoryFooter.tsx rename src/components/ChatList/{ChatHistoryThreadItem.tsx => ChatThreadItem.tsx} (100%) create mode 100644 src/components/ChatList/ChatThreadList.tsx diff --git a/src/components/ChatList/ChatHistoryDrawer.tsx b/src/components/ChatList/ChatHistoryDrawer.tsx index 50e7991..a0ad48d 100644 --- a/src/components/ChatList/ChatHistoryDrawer.tsx +++ b/src/components/ChatList/ChatHistoryDrawer.tsx @@ -1,16 +1,10 @@ import AddIcon from "@mui/icons-material/Add"; -import Brightness4Icon from "@mui/icons-material/Brightness4"; -import Brightness7Icon from "@mui/icons-material/Brightness7"; -import LogoutIcon from "@mui/icons-material/Logout"; import MenuIcon from "@mui/icons-material/Menu"; import { - Avatar, Button, Container, Drawer, Fab, - ListItemButton, - Skeleton, styled, } from "@mui/material"; import AppBar from "@mui/material/AppBar"; @@ -18,18 +12,14 @@ import Box from "@mui/material/Box"; import CssBaseline from "@mui/material/CssBaseline"; import Divider from "@mui/material/Divider"; import IconButton from "@mui/material/IconButton"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import ListItemIcon from "@mui/material/ListItemIcon"; -import ListItemText from "@mui/material/ListItemText"; import Toolbar from "@mui/material/Toolbar"; import { useEffect, useState } from "react"; -import { useAuthContext } from "../../context/AuthContext"; import { useSettings } from "../../context/SettingsContext"; import { useThreadContext } from "../../context/ThreadContext"; import ChatWindow from "../ChatWindow/ChatWindow"; -import { ThreadItem } from "./ChatHistoryThreadItem"; import { ChatSearch } from './ChatSearch'; +import { ChatThreadList } from './ChatThreadList'; +import { ChatHistoryFooter } from './ChatHistoryFooter'; const drawerWidth = 300; @@ -47,9 +37,8 @@ const Error = styled("div")(({ theme }) => ({ })); const ChatHistoryDrawer = () => { - const { logout, profile } = useAuthContext(); - const { darkMode, toggleDarkMode } = useSettings(); - const { threads, listThreads, createThread, loading, error } = + const { darkMode } = useSettings(); + const { listThreads, createThread, error } = useThreadContext(); // result of the search input filtering const [filteredThreadsIndexes, setFilteredThreadsIndexes] = useState([]); @@ -104,18 +93,10 @@ const ChatHistoryDrawer = () => { searchFilter={searchFilter} filteredThreadsIndexes={filteredThreadsIndexes} /> - - {[...Array(loading ? 4 : 0)].map((_, index) => ( - - - - ))} - {threads.filter((_it, idx) => (searchFilter.length === 0 - || filteredThreadsIndexes.includes(idx) - )).map((thread) => ( - - ))} - + )} @@ -124,27 +105,7 @@ const ChatHistoryDrawer = () => { - - - - - {darkMode ? : } - - - - - - - - - - - - - - - - + ); diff --git a/src/components/ChatList/ChatHistoryFooter.tsx b/src/components/ChatList/ChatHistoryFooter.tsx new file mode 100644 index 0000000..1e96cfc --- /dev/null +++ b/src/components/ChatList/ChatHistoryFooter.tsx @@ -0,0 +1,41 @@ +import Brightness4Icon from "@mui/icons-material/Brightness4"; +import Brightness7Icon from "@mui/icons-material/Brightness7"; +import LogoutIcon from "@mui/icons-material/Logout"; +import { + Avatar, + ListItemButton, +} from "@mui/material"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import { useAuthContext } from "../../context/AuthContext"; +import { useSettings } from "../../context/SettingsContext"; + +export const ChatHistoryFooter = () => { + const { logout, profile } = useAuthContext(); + const { darkMode, toggleDarkMode } = useSettings(); + + return ( + + + + + {darkMode ? : } + + + + + + + + + + + + + + + + ); +}; \ No newline at end of file diff --git a/src/components/ChatList/ChatHistoryThreadItem.tsx b/src/components/ChatList/ChatThreadItem.tsx similarity index 100% rename from src/components/ChatList/ChatHistoryThreadItem.tsx rename to src/components/ChatList/ChatThreadItem.tsx diff --git a/src/components/ChatList/ChatThreadList.tsx b/src/components/ChatList/ChatThreadList.tsx new file mode 100644 index 0000000..6b19bae --- /dev/null +++ b/src/components/ChatList/ChatThreadList.tsx @@ -0,0 +1,37 @@ +import { + Skeleton, +} from "@mui/material"; +import PropTypes from 'prop-types'; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import { useThreadContext } from "../../context/ThreadContext"; +import { ThreadItem } from "./ChatThreadItem"; + +export const ChatThreadList = ({ + searchFilter, + filteredThreadsIndexes +}: { + searchFilter: string[]; + filteredThreadsIndexes: number[]; +}) => { + const { threads, loading } = useThreadContext(); + + return ( + {[...Array(loading ? 4 : 0)].map((_, index) => ( + + + + ))} + {threads.filter((_it, idx) => (searchFilter.length === 0 + || filteredThreadsIndexes.includes(idx) + )).map((thread) => ( + + ))} + ); +}; + + +ChatThreadList.propTypes = { + filteredThreadsIndexes: PropTypes.array.isRequired, + searchFilter: PropTypes.array.isRequired, +}; \ No newline at end of file From 588e5fba1c7cd715465f0ba5b41198bc0aa176da Mon Sep 17 00:00:00 2001 From: Hugo Carreira Date: Fri, 14 Jun 2024 13:03:06 +0100 Subject: [PATCH 6/6] Minor refactoring --- src/components/ChatList/ChatSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ChatList/ChatSearch.tsx b/src/components/ChatList/ChatSearch.tsx index 9e1bc03..9d4080e 100644 --- a/src/components/ChatList/ChatSearch.tsx +++ b/src/components/ChatList/ChatSearch.tsx @@ -91,7 +91,7 @@ export const ChatSearch = ({ } for (const searchableThreadPart of searchableThread) { if (searchableThreadPart.includes(searchFilterPart)) { - if (!searchLogicalOperator || searchLogicalOperator === LogicalOperator.OR || searchFilter.length === 1) { + if (searchFilter.length === 1 || searchLogicalOperator === LogicalOperator.OR) { isIncluded = true; } else { andPartsFoundOnThread[i] = true;