diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts index db301e01..b12ed13b 100644 --- a/apps/web/src/actions/db.ts +++ b/apps/web/src/actions/db.ts @@ -15,7 +15,7 @@ import { like, eq, and, sql } from "drizzle-orm"; import { union } from "drizzle-orm/sqlite-core" // @todo: (future) pagination not yet needed -export async function searchMemoriesAndSpaces(query: string): Promise { +export async function searchMemoriesAndSpaces(query: string, opts?: { filter?: { memories?: boolean, spaces?: boolean }, range?: { offset: number, limit: number } }): Promise { const user = await getUser() @@ -31,7 +31,7 @@ export async function searchMemoriesAndSpaces(query: string): Promise`'space'`, @@ -42,9 +42,20 @@ export async function searchMemoriesAndSpaces(query: string): Promise q.offset(opts.range!.offset).limit(opts.range!.limit)) + } else { + queries = queries.map(q => q.all()) + } + + const data = await Promise.all(queries) return data.reduce((acc, i) => [...acc, ...i]) as SearchResult[] } catch { diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx index 886507ff..4f8ef734 100644 --- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx +++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx @@ -10,8 +10,11 @@ import { Input } from "../ui/input"; import { Label } from "../ui/label"; import { Markdown } from "tiptap-markdown"; import { useEffect, useRef, useState } from "react"; -import { FilterSpaces } from "./FilterCombobox"; +import { FilterMemories, FilterSpaces } from "./FilterCombobox"; import { useMemory } from "@/contexts/MemoryContext"; +import { Command, Plus, X } from "lucide-react"; +import { StoredContent } from "@/server/db/schema"; +import { cleanUrl } from "@/lib/utils"; export function AddMemoryPage() { const { addMemory } = useMemory(); @@ -153,29 +156,28 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) { } export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { - const [selectedSpacesId, setSelectedSpacesId] = useState([]); const inputRef = useRef(null); const [name, setName] = useState(""); - const [content, setContent] = useState(""); const [loading, setLoading] = useState(false); + const [selected, setSelected] = useState([]); + + function check(): boolean { const data = { name: name.trim(), - content, }; - console.log(name); if (!data.name || data.name.length < 1) { if (!inputRef.current) { alert("Please enter a name for the note"); return false; } inputRef.current.value = ""; - inputRef.current.placeholder = "Please enter a title for the note"; + inputRef.current.placeholder = "Please enter a title for the space"; inputRef.current.dataset["error"] = "true"; setTimeout(() => { - inputRef.current!.placeholder = "Title of the note"; + inputRef.current!.placeholder = "Enter the name of the space"; inputRef.current!.dataset["error"] = "false"; }, 500); inputRef.current.focus(); @@ -191,19 +193,48 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { setName(e.target.value)} + className="bg-rgray-4 mt-2 w-full focus-visible:data-[error=true]:ring-red-500/10 data-[error=true]:placeholder:text-red-400 placeholder:transition placeholder:duration-500" /> - + {selected.length > 0 && ( + <> + +
+ {selected.map(i => ( + setSelected(prev => prev.filter(p => p.id !== i.id))} + {...i} + /> + ))} +
+ + )} - + + Memory + + Cancel @@ -211,3 +242,16 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { ); } + +export function MemorySelectedItem({ id, title, url, image, onRemove }: StoredContent & { onRemove: () => void; }) { + return ( +
+ + + {title} + {cleanUrl(url)} +
+ ) +} diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx index bd432215..30463672 100644 --- a/apps/web/src/components/Sidebar/FilterCombobox.tsx +++ b/apps/web/src/components/Sidebar/FilterCombobox.tsx @@ -20,9 +20,11 @@ import { } from "@/components/ui/popover"; import { SpaceIcon } from "@/assets/Memories"; import { AnimatePresence, LayoutGroup, motion } from "framer-motion"; -import { useMemory } from "@/contexts/MemoryContext"; +import { SearchResult, useMemory } from "@/contexts/MemoryContext"; +import { useDebounce } from "@/hooks/useDebounce"; +import { StoredContent } from "@/server/db/schema"; -export interface Props extends React.ButtonHTMLAttributes { +export interface FilterSpacesProps extends React.ButtonHTMLAttributes { side?: "top" | "bottom"; align?: "end" | "start" | "center"; onClose?: () => void; @@ -42,7 +44,7 @@ export function FilterSpaces({ setSelectedSpaces, name, ...props -}: Props) { +}: FilterSpacesProps) { const { spaces } = useMemory(); const [open, setOpen] = React.useState(false); @@ -150,26 +152,58 @@ export function FilterSpaces({ ); } +export type FilterMemoriesProps = { + side?: "top" | "bottom"; + align?: "end" | "start" | "center"; + onClose?: () => void; + selected: StoredContent[]; + setSelected: React.Dispatch>; +} & React.ButtonHTMLAttributes + export function FilterMemories({ className, side = "bottom", align = "center", onClose, - selectedSpaces, - setSelectedSpaces, - name, + selected, + setSelected, ...props -}: Props) { - const { spaces } = useMemory(); - const [open, setOpen] = React.useState(false); +}: FilterMemoriesProps) { - const sortedSpaces = spaces.sort(({ id: a }, { id: b }) => - selectedSpaces.includes(a) && !selectedSpaces.includes(b) - ? -1 - : selectedSpaces.includes(b) && !selectedSpaces.includes(a) - ? 1 - : 0, - ); + const { search } = useMemory(); + + const [open, setOpen] = React.useState(false); + const [searchQuery, setSearchQuery] = React.useState(""); + const query = useDebounce(searchQuery, 500) + + const [searchResults, setSearchResults] = React.useState([]); + const [isSearching, setIsSearching] = React.useState(false) + + const results = React.useMemo(() => { + console.log("use memo") + return searchResults.map(r => r.memory) + }, [searchResults]) + + console.log('memoized', results) + + React.useEffect(() => { + const q = query.trim() + if (q.length > 0) { + setIsSearching(true); + (async () => { + const results = await search(q, { + filter: { + memories: true, + spaces: false + } + }) + setSearchResults(results) + setIsSearching(false) + })(); + } else { + setSearchResults([]) + } + }, [query]) React.useEffect(() => { if (!open) { @@ -177,6 +211,7 @@ export function FilterMemories({ } }, [open]); + console.log(searchResults); return ( @@ -191,15 +226,7 @@ export function FilterMemories({ )} {...props} > - - {name} - -
0} - className="on:flex text-rgray-11 border-rgray-6 bg-rgray-2 absolute left-0 top-0 hidden aspect-[1] h-4 w-4 -translate-x-1/3 -translate-y-1/3 items-center justify-center rounded-full border text-center text-[9px]" - > - {selectedSpaces.length} -
+ {props.children} - spaces - .find((s) => s.id.toString() === val) - ?.name.toLowerCase() - .includes(search.toLowerCase().trim()) - ? 1 - : 0 - } + shouldFilter={false} > - - - - Nothing found - - {sortedSpaces.map((space) => ( - { - setSelectedSpaces((prev: number[]) => - prev.includes(parseInt(val)) - ? prev.filter((v) => v !== parseInt(val)) - : [...prev, parseInt(val)], - ); - }} - asChild - > - - - {space.name} - {selectedSpaces.includes(space.id)} - - - - ))} - - - + + + + {isSearching ? "Searching..." : query.trim().length > 0 ? "Nothing Found" : "Search something"} + {results.map((m) => ( + { + setSelected((prev) => + prev.find(p => p.id === parseInt(val)) + ? prev.filter((v) => v.id !== parseInt(val)) + : [...prev, m], + ); + }} + asChild + > +
+ + {m.title} + i.id === m.id) !== undefined} + className={cn( + "on:opacity-100 ml-auto h-4 w-4 opacity-0", + )} + /> +
+
+ ))} + +
+
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx index 213667c8..1c9e7143 100644 --- a/apps/web/src/components/Sidebar/MemoriesBar.tsx +++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx @@ -60,7 +60,7 @@ export function MemoriesBar() { const [expandedSpace, setExpandedSpace] = useState(null); const [searchQuery, setSearcyQuery] = useState(""); const [searchLoading, setSearchLoading] = useState(false) - const query = useDebounce(searchQuery, 1000) + const query = useDebounce(searchQuery, 500) const [searchResults, setSearchResults] = useState([]) @@ -148,7 +148,7 @@ export function MemoriesBar() { ref={parent} className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5" > - {searchQuery.trim().length > 0 ? ( + {query.trim().length > 0 ? ( <> {searchResults.map(({ type, space, memory }, i) => ( <> diff --git a/apps/web/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx index 74b7f2e8..f3534b55 100644 --- a/apps/web/src/components/ui/command.tsx +++ b/apps/web/src/components/ui/command.tsx @@ -3,10 +3,11 @@ import * as React from "react"; import { type DialogProps } from "@radix-ui/react-dialog"; import { Command as CommandPrimitive } from "cmdk"; -import { Search } from "lucide-react"; +import { Loader, Search } from "lucide-react"; import { cn } from "@/lib/utils"; import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { isSea } from "node:sea"; const Command = React.forwardRef< React.ElementRef, @@ -39,13 +40,13 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => { const CommandInput = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { isSearching?: boolean } +>(({ className, isSearching = false ,...props }, ref) => (
- + {isSearching ? : } Promise; cachedMemories: ChachedSpaceContent[]; - search: (query: string) => Promise; + search: typeof searchMemoriesAndSpaces; }>({ spaces: [], freeMemories: [], @@ -57,15 +57,7 @@ export const MemoryProvider: React.FC< const deleteSpace = async (id: number) => { setSpaces((prev) => prev.filter((s) => s.id !== id)); } - - const search = async (query: string) => { - if (!user.id) { - throw new Error('user id is not define') - } - const data = await searchMemoriesAndSpaces(query) - return data as SearchResult[] - } - + // const fetchMemories = useCallback(async (query: string) => { // const response = await fetch(`/api/memories?${query}`); // }, []); @@ -80,7 +72,7 @@ export const MemoryProvider: React.FC< return (