Skip to content

Feat: Chat Search #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"classnames": "^2.3.2",
"fuse.js": "^6.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
62 changes: 50 additions & 12 deletions chat/src/Components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { IMessage } from "../../types";
import { Message } from "../Message";
import classNames from "classnames";
import { ReloadIcon } from "../../assets/Icons/Reload";
import { SearchDialog } from "../SearchDialog";
import Fuse from "fuse.js";
import { NoChatResultIllustration } from "../../assets/Illustrations/NoChatResult";

export const Chat: FC<{
messages: IMessage[];
Expand All @@ -13,6 +16,28 @@ export const Chat: FC<{
}> = ({ messages, focusedMessage, setFocusedMessage, onAction, filter }) => {
const messagesEndRef = useRef<HTMLDivElement | null>(null);
const [autoScroll, setAutoScroll] = useState<boolean>(true);
const [searchTerm, setSearchTerm] = useState("");
const [filteredMessages, setFilteredMessages] =
useState<IMessage[]>(messages);
const messagesToRender = searchTerm.trim() ? filteredMessages : messages;

// This function is called whenever the search term changes in the SearchDialog
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name itself implies what this does, so no need for the comment.

const handleSearchChange = (newSearchTerm: string) => {
setSearchTerm(newSearchTerm);

// Fuse.js options; adjust as needed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary comment.

const options = {
keys: ["content", "author", "platform"],
threshold: 0.4,
ignoreLocation: true,
};

const fuse = new Fuse(messages, options);
const result = fuse.search(newSearchTerm);

const matches = result.map(({ item }) => item); // Extract the matched items
setFilteredMessages(matches); // Set filtered messages
};

const handleFocusMessage = (message: IMessage) => {
setFocusedMessage(message.id === focusedMessage?.id ? null : message);
Expand Down Expand Up @@ -41,18 +66,27 @@ export const Chat: FC<{
focusedMessage,
})}
>
{messages.map((message: IMessage, index: number) => {
return (
<Message
key={index}
message={message}
filter={filter}
focusedMessage={focusedMessage}
onMessageClick={handleFocusMessage}
onAction={onAction}
/>
);
})}
{messagesToRender.length === 0 ? (
<div className="flex flex-col items-center justify-center text-center py-4 text-3xl text-gray-300">
It's quiet... too quiet. Try another search?
<div className="mt-12">
<NoChatResultIllustration />
</div>
</div>
) : (
messagesToRender.map(
(message: IMessage, index: number) => (
<Message
key={index}
message={message}
filter={filter}
focusedMessage={focusedMessage}
onMessageClick={handleFocusMessage}
onAction={onAction}
/>
)
)
)}
<div ref={messagesEndRef}></div>
</div>
</div>
Expand All @@ -69,6 +103,10 @@ export const Chat: FC<{
/>
</div>
)}
<SearchDialog
onSearchChange={handleSearchChange}
searchTerm={searchTerm}
/>
</>
);
};
120 changes: 120 additions & 0 deletions chat/src/Components/SearchDialog/SearchDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, { useState, useEffect, useRef } from "react";

type SearchDialogProps = {
onSearchChange: (newSearchTerm: string) => void;
searchTerm: string;
};

export const SearchDialog: React.FC<SearchDialogProps> = ({
onSearchChange,
searchTerm,
}) => {
const [inputValue, setInputValue] = useState(searchTerm);
const inputRef = useRef<HTMLInputElement>(null);
const [isSearchDialogOpen, setIsSearchDialogOpen] = useState(false);

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newSearchTerm = event.target.value;
setInputValue(newSearchTerm);
onSearchChange(newSearchTerm);
};

const clearInput = () => {
setInputValue("");
onSearchChange("");
inputRef.current?.focus();
};

const handleKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key === "f") {
event.preventDefault();
setIsSearchDialogOpen((prev) => !prev);
}
};

useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);

const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === e.currentTarget) {
setIsSearchDialogOpen(false);
}
};

useEffect(() => {
inputRef.current?.focus();
}, [isSearchDialogOpen]);

return (
isSearchDialogOpen && (
<div
className="fixed inset-0 z-50 flex items-end"
onClick={handleBackdropClick}
>
<div
className="bg-[#14171a] w-2/3 md:w-2/3 lg:w-2/3 shadow-lg rounded-lg overflow-hidden mb-6 mx-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="searchbar px-6 py-4">
<div className="mt-2 text-2xl text-gray-400">
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg
className="w-10 h-10 text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
</div>
<input
type="text"
id="default-search"
ref={inputRef}
value={inputValue}
onChange={handleInputChange}
className="block w-full p-4 pl-20 text-1xl text-gray-300 border border-gray-300 rounded-lg bg-transparent"
placeholder="Search Messages, Viewers..."
required
/>
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
<button
onClick={clearInput}
className="absolute right-3 text-gray-400"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-10 w-10"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
)
);
};
1 change: 1 addition & 0 deletions chat/src/Components/SearchDialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./SearchDialog";
122 changes: 122 additions & 0 deletions chat/src/assets/Illustrations/NoChatResult.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
export function NoChatResultIllustration() {
const containerStyles = {
width: "50vw",
height: "50vh",
overflow: "hidden",
};

const svgStyles = {
width: "100%",
height: "100%",
};

return (
<div style={containerStyles}>
<svg
style={svgStyles}
xmlns="http://www.w3.org/2000/svg"
data-name="Layer 1"
viewBox="0 0 430.91406 559.70956"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<path
d="M689.29823,324.68445q0,4.785-.31006,9.49a143.75442,143.75442,0,0,1-13.46973,52.19c-.06006.14-.13037.27-.18994.4-.36035.76-.73047,1.52-1.11035,2.27a142.03868,142.03868,0,0,1-7.6499,13.5,144.462,144.462,0,0,1-118.56006,66.72l1.43018,82.24,18.6499-9.82,3.33008,6.33-21.83985,11.5,2.66992,152.74.02979,2.04-14.41992,1.21.02978-.05,4.54-246.18a144.17482,144.17482,0,0,1-102-44.38c-.90967-.94-1.81006-1.91-2.68994-2.87-.04-.04-.06982-.08-.1001-.11a144.76758,144.76758,0,0,1-26.33984-40.76c.14014.16.29.31.43017.47a144.642,144.642,0,0,1,68.57959-186.38c.5-.25,1.01026-.49,1.51026-.74a144.75207,144.75207,0,0,1,187.52978,56.93c.88037,1.48005,1.73047,2.99006,2.5503,4.51A143.85218,143.85218,0,0,1,689.29823,324.68445Z"
transform="translate(-384.54297 -170.14522)"
fill="#e5e5e5"
/>
<circle
cx="198.2848"
cy="502.61836"
r="43.06733"
fill="#2f2e41"
/>
<rect
x="210.6027"
y="532.22265"
width="38.58356"
height="13.08374"
fill="#2f2e41"
/>
<ellipse
cx="249.45884"
cy="534.4033"
rx="4.08868"
ry="10.90314"
fill="#2f2e41"
/>
<rect
x="201.6027"
y="531.22265"
width="38.58356"
height="13.08374"
fill="#2f2e41"
/>
<ellipse
cx="240.45884"
cy="533.4033"
rx="4.08868"
ry="10.90314"
fill="#2f2e41"
/>
<path
d="M541.051,632.71229c-3.47748-15.5738,7.63866-31.31043,24.82866-35.14881s33.94421,5.67511,37.42169,21.2489-7.91492,21.31769-25.10486,25.156S544.5285,648.28608,541.051,632.71229Z"
transform="translate(-384.54297 -170.14522)"
fill="#6c63ff"
/>
<path
d="M599.38041,670.31119a10.75135,10.75135,0,0,1-10.33984-7.12305,1,1,0,0,1,1.896-.63672c1.51416,4.50782,6.69825,6.86524,11.55457,5.25342a9.60826,9.60826,0,0,0,5.57251-4.74756,8.23152,8.23152,0,0,0,.48547-6.33789,1,1,0,0,1,1.896-.63672,10.217,10.217,0,0,1-.59229,7.86817,11.62362,11.62362,0,0,1-6.73218,5.75244A11.87976,11.87976,0,0,1,599.38041,670.31119Z"
transform="translate(-384.54297 -170.14522)"
fill="#fff"
/>
<path
d="M618.56452,676.16463a9.57244,9.57244,0,1,1-17.04506,8.71737h0l-.00855-.01674c-2.40264-4.70921.91734-7.63227,5.62657-10.03485S616.162,671.45547,618.56452,676.16463Z"
transform="translate(-384.54297 -170.14522)"
fill="#fff"
/>
<path
d="M772.27559,716.2189h-381a1,1,0,0,1,0-2h381a1,1,0,0,1,0,2Z"
transform="translate(-384.54297 -170.14522)"
fill="#3f3d56"
/>
<ellipse
cx="567.22606"
cy="706.64241"
rx="7.50055"
ry="23.89244"
transform="translate(-543.03826 -6.10526) rotate(-14.4613)"
fill="#2f2e41"
/>
<path
d="M645.50888,621.42349H629.12323a.77274.77274,0,0,1-.51881-1.3455l14.90017-13.49467h-13.7669a.77274.77274,0,0,1,0-1.54548h15.77119a.77275.77275,0,0,1,.51881,1.34551L631.12753,619.878h14.38135a.77274.77274,0,1,1,0,1.54548Z"
transform="translate(-384.54297 -170.14522)"
fill="#cbcbcb"
/>
<path
d="M666.37288,597.46853H649.98723a.77275.77275,0,0,1-.51881-1.34551l14.90017-13.49466h-13.7669a.77274.77274,0,0,1,0-1.54548h15.77119a.77274.77274,0,0,1,.51881,1.3455l-14.90016,13.49467h14.38135a.77274.77274,0,1,1,0,1.54548Z"
transform="translate(-384.54297 -170.14522)"
fill="#cbcbcb"
/>
<path
d="M657.1,571.19534H640.71434a.77274.77274,0,0,1-.51881-1.3455l14.90017-13.49467H641.3288a.77274.77274,0,0,1,0-1.54548H657.1a.77275.77275,0,0,1,.51881,1.34551l-14.90016,13.49466H657.1a.77274.77274,0,0,1,0,1.54548Z"
transform="translate(-384.54297 -170.14522)"
fill="#cbcbcb"
/>
<path
d="M770.66217,347.522,783.457,337.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,770.66217,347.522Z"
transform="translate(-384.54297 -170.14522)"
fill="#3f3d56"
/>
<path
d="M403.66217,180.522,416.457,170.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,403.66217,180.522Z"
transform="translate(-384.54297 -170.14522)"
fill="#3f3d56"
/>
<path
d="M802.66217,215.522,815.457,205.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,802.66217,215.522Z"
transform="translate(-384.54297 -170.14522)"
fill="#3f3d56"
/>
</svg>
</div>
);
}