diff options
| author | yxshv <[email protected]> | 2024-04-13 15:01:51 +0530 |
|---|---|---|
| committer | yxshv <[email protected]> | 2024-04-13 15:01:51 +0530 |
| commit | 070ead455120d6da3d0a6843cb0d842a65359c43 (patch) | |
| tree | 67196c3aaec0a07e0b0151f21582fe22ad068ae7 /apps/web/src | |
| parent | memory item (diff) | |
| download | supermemory-070ead455120d6da3d0a6843cb0d842a65359c43.tar.xz supermemory-070ead455120d6da3d0a6843cb0d842a65359c43.zip | |
search results
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/actions/db.ts | 61 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/MemoriesBar.tsx | 70 | ||||
| -rw-r--r-- | apps/web/src/contexts/MemoryContext.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/hooks/useDebounce.ts | 27 |
4 files changed, 122 insertions, 38 deletions
diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts index 46e3ddf6..db301e01 100644 --- a/apps/web/src/actions/db.ts +++ b/apps/web/src/actions/db.ts @@ -6,36 +6,51 @@ import { sessions, StoredContent, storedContent, + StoredSpace, users, space } from "@/server/db/schema"; +import { SearchResult } from "@/contexts/MemoryContext"; import { like, eq, and, sql } from "drizzle-orm"; import { union } from "drizzle-orm/sqlite-core" -import { auth as authOptions } from "@/server/auth"; // @todo: (future) pagination not yet needed -export async function searchMemoriesAndSpaces(userId: string, query: string) { - const searchMemoriesQuery = db.select({ - type: sql<string>`'memory'`, - space: sql`NULL`, - memory: storedContent as any - }).from(storedContent).where(and( - eq(storedContent.user, userId), - like(storedContent.title, `%${query}%`) - )) - - const searchSpacesQuery = db.select({ - type: sql<string>`'space'`, - space: space as any, - memory: sql`NULL`, - }).from(space).where( - and( - eq(space.user, userId), - like(space.name, `%${query}%`) - ) - ) - - return await union(searchMemoriesQuery, searchSpacesQuery) +export async function searchMemoriesAndSpaces(query: string): Promise<SearchResult[]> { + + const user = await getUser() + + if (!user) { + return [] + } + + try { + const searchMemoriesQuery = db.select({ + type: sql<string>`'memory'`, + space: sql`NULL`, + memory: storedContent as any + }).from(storedContent).where(and( + eq(storedContent.user, user.id), + like(storedContent.title, `%${query}%`) + )).all() + + const searchSpacesQuery = db.select({ + type: sql<string>`'space'`, + space: space as any, + memory: sql`NULL`, + }).from(space).where( + and( + eq(space.user, user.id), + like(space.name, `%${query}%`) + ) + ).all() + + const data = await Promise.all([searchSpacesQuery, searchMemoriesQuery]) + + return data.reduce((acc, i) => [...acc, ...i]) as SearchResult[] + } catch { + return [] + } + } async function getUser() { diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx index 1b3c718e..213667c8 100644 --- a/apps/web/src/components/Sidebar/MemoriesBar.tsx +++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx @@ -10,6 +10,7 @@ import { Input, InputWithIcon } from "../ui/input"; import { ArrowUpRight, Edit3, + Loader, MoreHorizontal, Plus, Search, @@ -25,7 +26,7 @@ import { } from "../ui/dropdown-menu"; import { useEffect, useMemo, useRef, useState } from "react"; import { Variant, useAnimate, motion } from "framer-motion"; -import { useMemory } from "@/contexts/MemoryContext"; +import { SearchResult, useMemory } from "@/contexts/MemoryContext"; import { SpaceIcon } from "@/assets/Memories"; import { Dialog, @@ -44,10 +45,12 @@ import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog"; import { ExpandedSpace } from "./ExpandedSpace"; import { StoredContent, StoredSpace } from "@/server/db/schema"; import Image from "next/image" +import { useDebounce } from "@/hooks/useDebounce"; +import { searchMemoriesAndSpaces } from "@/actions/db"; export function MemoriesBar() { const [parent, enableAnimations] = useAutoAnimate(); - const { spaces, deleteSpace, freeMemories } = useMemory(); + const { spaces, deleteSpace, freeMemories, search } = useMemory(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [addMemoryState, setAddMemoryState] = useState< @@ -55,6 +58,11 @@ export function MemoriesBar() { >(null); const [expandedSpace, setExpandedSpace] = useState<number | null>(null); + const [searchQuery, setSearcyQuery] = useState(""); + const [searchLoading, setSearchLoading] = useState(false) + const query = useDebounce(searchQuery, 1000) + + const [searchResults, setSearchResults] = useState<SearchResult[]>([]) if (expandedSpace) { return ( @@ -65,14 +73,31 @@ export function MemoriesBar() { ); } + useEffect(() => { + const q = query.trim() + if (q.length < 1) { + setSearchResults([]) + return + } + + setSearchLoading(true); + + (async () => { + setSearchResults(await search(q)) + setSearchLoading(false) + })(); + }, [query]) + return ( <div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left"> <div className="w-full px-8"> <h1 className="w-full text-2xl">Your Memories</h1> <InputWithIcon placeholder="Search" - icon={<Search className="text-rgray-11 h-5 w-5 opacity-50" />} + icon={searchLoading ? <Loader className="text-rgray-11 h-5 w-5 opacity-50 animate-spin" /> : <Search className="text-rgray-11 h-5 w-5 opacity-50" />} className="bg-rgray-4 mt-2 w-full" + value={searchQuery} + onChange={(e) => setSearcyQuery(e.target.value)} /> </div> <div className="mt-2 flex w-full px-8"> @@ -123,17 +148,34 @@ export function MemoriesBar() { ref={parent} className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5" > - {spaces.map((space) => ( - <SpaceItem - onDelete={() => {}} - key={space.id} - //onClick={() => setExpandedSpace(space.id)} - {...space} - /> - ))} - {freeMemories.map(m => ( - <MemoryItem {...m} key={m.id} /> - ))} + {searchQuery.trim().length > 0 ? ( + <> + {searchResults.map(({ type, space, memory }, i) => ( + <> + {type === "memory" && ( + <MemoryItem {...memory!} key={i} /> + )} + {type === "space" && ( + <SpaceItem {...space!} key={i} onDelete={() => {}} /> + )} + </> + ))} + </> + ): ( + <> + {spaces.map((space) => ( + <SpaceItem + onDelete={() => {}} + key={space.id} + //onClick={() => setExpandedSpace(space.id)} + {...space} + /> + ))} + {freeMemories.map(m => ( + <MemoryItem {...m} key={m.id} /> + ))} + </> + )} </div> </div> ); diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx index a6050332..b805d41e 100644 --- a/apps/web/src/contexts/MemoryContext.tsx +++ b/apps/web/src/contexts/MemoryContext.tsx @@ -62,7 +62,7 @@ export const MemoryProvider: React.FC< if (!user.id) { throw new Error('user id is not define') } - const data = await searchMemoriesAndSpaces(user.id, query) + const data = await searchMemoriesAndSpaces(query) return data as SearchResult[] } diff --git a/apps/web/src/hooks/useDebounce.ts b/apps/web/src/hooks/useDebounce.ts new file mode 100644 index 00000000..4a47de72 --- /dev/null +++ b/apps/web/src/hooks/useDebounce.ts @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; + +/** + * Use this hook when you need to debounce a value. + * @param value + * @param delay in milliseconds + */ +export const useDebounce = <T>(value: T, delay: number) => { + // State and setters for debounced value + const [debouncedValue, setDebouncedValue] = useState<T>(value); + + useEffect(() => { + // Update debounced value after delay + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + // Cancel the timeout if value changes (also on delay change or unmount) + // This is how we prevent debounced value from updating if the value is changed + // within the delay period. Timeout gets cleared and restarted. + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +}; |