diff options
| author | yxshv <[email protected]> | 2024-04-14 00:32:04 +0530 |
|---|---|---|
| committer | yxshv <[email protected]> | 2024-04-14 00:32:04 +0530 |
| commit | e0461696fc9732c240f48d5be6e824a1d5fced44 (patch) | |
| tree | 92e3e91099119db07398b1419f37d144c1d78ccc /apps/web/src/components | |
| parent | fix relaod bug (diff) | |
| parent | fix build fail (diff) | |
| download | supermemory-e0461696fc9732c240f48d5be6e824a1d5fced44.tar.xz supermemory-e0461696fc9732c240f48d5be6e824a1d5fced44.zip | |
Merge branch 'main' of https://github.com/dhravya/supermemory
Diffstat (limited to 'apps/web/src/components')
| -rw-r--r-- | apps/web/src/components/Main.tsx | 5 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/AddMemoryDialog.tsx | 267 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/FilterCombobox.tsx | 151 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/MemoriesBar.tsx | 405 | ||||
| -rw-r--r-- | apps/web/src/components/ui/command.tsx | 13 |
5 files changed, 448 insertions, 393 deletions
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx index 3c883b67..e6b31de5 100644 --- a/apps/web/src/components/Main.tsx +++ b/apps/web/src/components/Main.tsx @@ -380,7 +380,10 @@ export function Chat({ loading={i === chatHistory.length - 1 ? isLoading : false} sources={msg.answer.sources} > - {msg.answer.parts.map((part) => part.text).join(" ")} + {msg.answer.parts + .map((part) => part.text) + .join("") + .replace("</s>", "")} </ChatAnswer> </ChatMessage> ))} diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx index f21a9683..39f088e3 100644 --- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx +++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx @@ -15,7 +15,7 @@ import { useMemory } from "@/contexts/MemoryContext"; import { Loader, Plus, X } from "lucide-react"; import { StoredContent } from "@/server/db/schema"; import { cleanUrl } from "@/lib/utils"; -import { motion } from "framer-motion" +import { motion } from "framer-motion"; import { getMetaData } from "@/server/helpers"; export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) { @@ -39,29 +39,29 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) { placeholder="Enter the URL of the page" type="url" data-modal-autofocus - className="disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 mt-2 w-full" + className="bg-rgray-4 mt-2 w-full disabled:cursor-not-allowed disabled:opacity-70" value={url} onChange={(e) => setUrl(e.target.value)} - disabled={loading} + disabled={loading} /> <DialogFooter> <FilterSpaces selectedSpaces={selectedSpacesId} setSelectedSpaces={setSelectedSpacesId} - className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-5 mr-auto bg-white/5" + className="hover:bg-rgray-5 mr-auto bg-white/5 disabled:cursor-not-allowed disabled:opacity-70" name={"Spaces"} - disabled={loading} + disabled={loading} /> <button type={"submit"} - disabled={loading} + disabled={loading} onClick={async () => { - setLoading(true) - const metadata = await getMetaData(url) + setLoading(true); + const metadata = await getMetaData(url); await addMemory( { title: metadata.title, - description: metadata.description, + description: metadata.description, content: "", type: "page", url: url, @@ -70,28 +70,28 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) { }, selectedSpacesId, ); - closeDialog() + closeDialog(); }} - className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2" + className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70" > - <motion.div - initial={{ x: '-50%', y: '-100%' }} - animate={loading && { y: '-50%', x: '-50%', opacity: 1 }} - className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2" - > - <Loader className="w-5 h-5 animate-spin text-rgray-11" /> - </motion.div> - <motion.div - initial={{ y: '0%' }} - animate={loading && { opacity: 0, y: '30%' }} - > - Add - </motion.div> + <motion.div + initial={{ x: "-50%", y: "-100%" }} + animate={loading && { y: "-50%", x: "-50%", opacity: 1 }} + className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0" + > + <Loader className="text-rgray-11 h-5 w-5 animate-spin" /> + </motion.div> + <motion.div + initial={{ y: "0%" }} + animate={loading && { opacity: 0, y: "30%" }} + > + Add + </motion.div> </button> <DialogClose - disabled={loading} - className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2" - > + disabled={loading} + className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70" + > Cancel </DialogClose> </DialogFooter> @@ -100,8 +100,7 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) { } export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) { - - const { addMemory } = useMemory() + const { addMemory } = useMemory(); const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>([]); @@ -142,7 +141,7 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) { placeholder="Title of the note" data-modal-autofocus value={name} - disabled={loading} + disabled={loading} onChange={(e) => setName(e.target.value)} /> <Editor @@ -165,39 +164,41 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) { <button onClick={() => { if (check()) { - setLoading(true) - addMemory({ - content, - title: name, - type: "note", - url: "https://notes.supermemory.dhr.wtf/", - image: '', - savedAt: new Date() - }, selectedSpacesId).then(closeDialog) + setLoading(true); + addMemory( + { + content, + title: name, + type: "note", + url: "https://notes.supermemory.dhr.wtf/", + image: "", + savedAt: new Date(), + }, + selectedSpacesId, + ).then(closeDialog); } }} - disabled={loading} - className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2" + disabled={loading} + className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70" > - - <motion.div - initial={{ x: '-50%', y: '-100%' }} - animate={loading && { y: '-50%', x: '-50%', opacity: 1 }} - className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2" - > - <Loader className="w-5 h-5 animate-spin text-rgray-11" /> - </motion.div> - <motion.div - initial={{ y: '0%' }} - animate={loading && { opacity: 0, y: '30%' }} - > - Add - </motion.div> + <motion.div + initial={{ x: "-50%", y: "-100%" }} + animate={loading && { y: "-50%", x: "-50%", opacity: 1 }} + className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0" + > + <Loader className="text-rgray-11 h-5 w-5 animate-spin" /> + </motion.div> + <motion.div + initial={{ y: "0%" }} + animate={loading && { opacity: 0, y: "30%" }} + > + Add + </motion.div> </button> <DialogClose type={undefined} - disabled={loading} - className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2" + disabled={loading} + className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70" > Cancel </DialogClose> @@ -207,16 +208,14 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) { } export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { - - const { addSpace } = useMemory() + const { addSpace } = useMemory(); const inputRef = useRef<HTMLInputElement>(null); const [name, setName] = useState(""); const [loading, setLoading] = useState(false); - const [selected, setSelected] = useState<StoredContent[]>([]); - + const [selected, setSelected] = useState<StoredContent[]>([]); function check(): boolean { const data = { @@ -247,65 +246,73 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { </DialogHeader> <Label className="mt-5 block">Name</Label> <Input - ref={inputRef} + ref={inputRef} placeholder="Enter the name of the space" type="url" data-modal-autofocus - value={name} - disabled={loading} - onChange={e => 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" + value={name} + disabled={loading} + onChange={(e) => setName(e.target.value)} + className="bg-rgray-4 mt-2 w-full placeholder:transition placeholder:duration-500 data-[error=true]:placeholder:text-red-400 focus-visible:data-[error=true]:ring-red-500/10" /> {selected.length > 0 && ( - <> - <Label className="mt-5 block">Add Memories</Label> - <div className="flex min-h-5 py-2 flex-col justify-center items-center"> - {selected.map(i => ( - <MemorySelectedItem - key={i.id} - onRemove={() => setSelected(prev => prev.filter(p => p.id !== i.id))} - {...i} - /> - ))} - </div> - </> - )} + <> + <Label className="mt-5 block">Add Memories</Label> + <div className="flex min-h-5 flex-col items-center justify-center py-2"> + {selected.map((i) => ( + <MemorySelectedItem + key={i.id} + onRemove={() => + setSelected((prev) => prev.filter((p) => p.id !== i.id)) + } + {...i} + /> + ))} + </div> + </> + )} <DialogFooter> - <FilterMemories - selected={selected} - setSelected={setSelected} - disabled={loading} - className="mr-auto bg-white/5 hover:bg-rgray-4 focus-visible:bg-rgray-4 disabled:opacity-70 disabled:cursor-not-allowed" - > - <Plus className="w-5 h-5" /> - Memory - </FilterMemories> + <FilterMemories + selected={selected} + setSelected={setSelected} + disabled={loading} + className="hover:bg-rgray-4 focus-visible:bg-rgray-4 mr-auto bg-white/5 disabled:cursor-not-allowed disabled:opacity-70" + > + <Plus className="h-5 w-5" /> + Memory + </FilterMemories> <button type={undefined} - onClick={() => { - if (check()) { - setLoading(true) - addSpace(name, selected.map(s => s.id)).then(() => closeDialog()) - } - }} - disabled={loading} - className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2" + onClick={() => { + if (check()) { + setLoading(true); + addSpace( + name, + selected.map((s) => s.id), + ).then(() => closeDialog()); + } + }} + disabled={loading} + className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70" > - <motion.div - initial={{ x: '-50%', y: '-100%' }} - animate={loading && { y: '-50%', x: '-50%', opacity: 1 }} - className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2" - > - <Loader className="w-5 h-5 animate-spin text-rgray-11" /> - </motion.div> - <motion.div - initial={{ y: '0%' }} - animate={loading && { opacity: 0, y: '30%' }} - > - Add - </motion.div> + <motion.div + initial={{ x: "-50%", y: "-100%" }} + animate={loading && { y: "-50%", x: "-50%", opacity: 1 }} + className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0" + > + <Loader className="text-rgray-11 h-5 w-5 animate-spin" /> + </motion.div> + <motion.div + initial={{ y: "0%" }} + animate={loading && { opacity: 0, y: "30%" }} + > + Add + </motion.div> </button> - <DialogClose disabled={loading} className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"> + <DialogClose + disabled={loading} + className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70" + > Cancel </DialogClose> </DialogFooter> @@ -313,15 +320,33 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) { ); } -export function MemorySelectedItem({ id, title, url, type, image, onRemove }: StoredContent & { onRemove: () => void; }) { - return ( - <div className="flex justify-start gap-2 p-1 px-2 w-full items-center text-sm rounded-md hover:bg-rgray-4 focus-within-bg-rgray-4 [&:hover>[data-icon]]:block [&:hover>img]:hidden"> - <img src={type === 'note'? '/note.svg' : image ?? "/icons/logo_without_bg.png"} className="h-5 w-5" /> - <button onClick={onRemove} data-icon className="w-5 h-5 p-0 m-0 hidden focus-visible:outline-none"> - <X className="w-5 h-5 scale-90" /> - </button> - <span>{title}</span> - <span className="ml-auto block opacity-50">{type ==='note' ? 'Note' : cleanUrl(url)}</span> - </div> - ) +export function MemorySelectedItem({ + id, + title, + url, + type, + image, + onRemove, +}: StoredContent & { onRemove: () => void }) { + return ( + <div className="hover:bg-rgray-4 focus-within-bg-rgray-4 flex w-full items-center justify-start gap-2 rounded-md p-1 px-2 text-sm [&:hover>[data-icon]]:block [&:hover>img]:hidden"> + <img + src={ + type === "note" ? "/note.svg" : image ?? "/icons/logo_without_bg.png" + } + className="h-5 w-5" + /> + <button + onClick={onRemove} + data-icon + className="m-0 hidden h-5 w-5 p-0 focus-visible:outline-none" + > + <X className="h-5 w-5 scale-90" /> + </button> + <span>{title}</span> + <span className="ml-auto block opacity-50"> + {type === "note" ? "Note" : cleanUrl(url)} + </span> + </div> + ); } diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx index de2d5fe8..f93ae710 100644 --- a/apps/web/src/components/Sidebar/FilterCombobox.tsx +++ b/apps/web/src/components/Sidebar/FilterCombobox.tsx @@ -24,7 +24,8 @@ import { SearchResult, useMemory } from "@/contexts/MemoryContext"; import { useDebounce } from "@/hooks/useDebounce"; import { StoredContent } from "@/server/db/schema"; -export interface FilterSpacesProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { +export interface FilterSpacesProps + extends React.ButtonHTMLAttributes<HTMLButtonElement> { side?: "top" | "bottom"; align?: "end" | "start" | "center"; onClose?: () => void; @@ -153,7 +154,7 @@ export type FilterMemoriesProps = { onClose?: () => void; selected: StoredContent[]; setSelected: React.Dispatch<React.SetStateAction<StoredContent[]>>; -} & React.ButtonHTMLAttributes<HTMLButtonElement> +} & React.ButtonHTMLAttributes<HTMLButtonElement>; export function FilterMemories({ className, @@ -164,40 +165,39 @@ export function FilterMemories({ setSelected, ...props }: FilterMemoriesProps) { - - const { search } = useMemory(); + const { search } = useMemory(); const [open, setOpen] = React.useState(false); - const [searchQuery, setSearchQuery] = React.useState(""); - const query = useDebounce(searchQuery, 500) + const [searchQuery, setSearchQuery] = React.useState(""); + const query = useDebounce(searchQuery, 500); - const [searchResults, setSearchResults] = React.useState<SearchResult[]>([]); - const [isSearching, setIsSearching] = React.useState(false) + const [searchResults, setSearchResults] = React.useState<SearchResult[]>([]); + const [isSearching, setIsSearching] = React.useState(false); - const results = React.useMemo(() => { - return searchResults.map(r => r.memory) - }, [searchResults]) + const results = React.useMemo(() => { + return searchResults.map((r) => r.memory); + }, [searchResults]); - console.log('memoized', results) + 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(() => { + 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) { @@ -205,7 +205,7 @@ export function FilterMemories({ } }, [open]); - console.log(searchResults); + console.log(searchResults); return ( <AnimatePresence mode="popLayout"> <LayoutGroup> @@ -220,7 +220,7 @@ export function FilterMemories({ )} {...props} > - {props.children} + {props.children} </button> </PopoverTrigger> <PopoverContent @@ -229,43 +229,58 @@ export function FilterMemories({ side={side} className="w-[200px] p-0" > - <Command - shouldFilter={false} - > - <CommandInput isSearching={isSearching} value={searchQuery} onValueChange={setSearchQuery} placeholder="Filter memories..." /> + <Command shouldFilter={false}> + <CommandInput + isSearching={isSearching} + value={searchQuery} + onValueChange={setSearchQuery} + placeholder="Filter memories..." + /> <CommandList> - <CommandGroup> - <CommandEmpty className="text-rgray-11 text-sm text-center py-5">{isSearching ? "Searching..." : query.trim().length > 0 ? "Nothing Found" : "Search something"}</CommandEmpty> - {results.map((m) => ( - <CommandItem - key={m.id} - value={m.id.toString()} - onSelect={(val) => { - setSelected((prev) => - prev.find(p => p.id === parseInt(val)) - ? prev.filter((v) => v.id !== parseInt(val)) - : [...prev, m], - ); - }} - asChild - > - <div - className="text-rgray-11" - > - <img src={m.type === 'note' ? '/note.svg' : m.image ?? "/icons/logo_without_bg.png"} className="mr-2 h-4 w-4" /> - {m.title} - <Check - data-state-on={selected.find(i => i.id === m.id) !== undefined} - className={cn( - "on:opacity-100 ml-auto h-4 w-4 opacity-0", - )} - /> - </div> - </CommandItem> - ))} - - </CommandGroup> - </CommandList> + <CommandGroup> + <CommandEmpty className="text-rgray-11 py-5 text-center text-sm"> + {isSearching + ? "Searching..." + : query.trim().length > 0 + ? "Nothing Found" + : "Search something"} + </CommandEmpty> + {results.map((m) => ( + <CommandItem + key={m.id} + value={m.id.toString()} + onSelect={(val) => { + setSelected((prev) => + prev.find((p) => p.id === parseInt(val)) + ? prev.filter((v) => v.id !== parseInt(val)) + : [...prev, m], + ); + }} + asChild + > + <div className="text-rgray-11"> + <img + src={ + m.type === "note" + ? "/note.svg" + : m.image ?? "/icons/logo_without_bg.png" + } + className="mr-2 h-4 w-4" + /> + {m.title} + <Check + data-state-on={ + selected.find((i) => i.id === m.id) !== undefined + } + className={cn( + "on:opacity-100 ml-auto h-4 w-4 opacity-0", + )} + /> + </div> + </CommandItem> + ))} + </CommandGroup> + </CommandList> </Command> </PopoverContent> </Popover> diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx index f474262a..6c640e26 100644 --- a/apps/web/src/components/Sidebar/MemoriesBar.tsx +++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx @@ -44,7 +44,7 @@ import { DialogTrigger } from "@radix-ui/react-dialog"; import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog"; import { ExpandedSpace } from "./ExpandedSpace"; import { StoredContent, StoredSpace } from "@/server/db/schema"; -import Image from "next/image" +import Image from "next/image"; import { useDebounce } from "@/hooks/useDebounce"; export function MemoriesBar() { @@ -57,11 +57,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, 500) + const [searchQuery, setSearcyQuery] = useState(""); + const [searchLoading, setSearchLoading] = useState(false); + const query = useDebounce(searchQuery, 500); - const [searchResults, setSearchResults] = useState<SearchResult[]>([]) + const [searchResults, setSearchResults] = useState<SearchResult[]>([]); if (expandedSpace) { return ( @@ -72,20 +72,20 @@ export function MemoriesBar() { ); } - useEffect(() => { - const q = query.trim() - if (q.length < 1) { - setSearchResults([]) - return - } + useEffect(() => { + const q = query.trim(); + if (q.length < 1) { + setSearchResults([]); + return; + } - setSearchLoading(true); + setSearchLoading(true); - (async () => { - setSearchResults(await search(q)) - setSearchLoading(false) - })(); - }, [query]) + (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"> @@ -93,10 +93,16 @@ export function MemoriesBar() { <h1 className="w-full text-2xl">Your Memories</h1> <InputWithIcon placeholder="Search" - 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" />} + icon={ + searchLoading ? ( + <Loader className="text-rgray-11 h-5 w-5 animate-spin opacity-50" /> + ) : ( + <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)} + value={searchQuery} + onChange={(e) => setSearcyQuery(e.target.value)} /> </div> <div className="mt-2 flex w-full px-8"> @@ -147,34 +153,32 @@ export function MemoriesBar() { ref={parent} className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5" > - {query.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={() => deleteSpace(space.id)} - key={space.id} - //onClick={() => setExpandedSpace(space.id)} - {...space} - /> - ))} - {freeMemories.map(m => ( - <MemoryItem {...m} key={m.id} /> - ))} - </> - )} + {query.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={() => deleteSpace(space.id)} + key={space.id} + //onClick={() => setExpandedSpace(space.id)} + {...space} + /> + ))} + {freeMemories.map((m) => ( + <MemoryItem {...m} key={m.id} /> + ))} + </> + )} </div> </div> ); @@ -190,44 +194,40 @@ const SpaceExitVariant: Variant = { }, }; -export function MemoryItem({ - id, - title, - image, - type -}: StoredContent) { +export function MemoryItem({ id, title, image, type }: StoredContent) { + const name = title + ? title.length > 10 + ? title.slice(0, 10) + "..." + : title + : "<no title>"; - const name = title ? title.length > 10 ? title.slice(0, 10) + "..." : title : '<no title>'; - - return ( - <div - - className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100" - > - <button data-space-text className="focus-visible:outline-none"> + return ( + <div className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"> + <button data-space-text className="focus-visible:outline-none"> {name} </button> - - <div className="w-24 h-24 flex justify-center items-center"> - {type === "page" ? ( - <img - className="h-16 w-16" - id={id.toString()} - src={image!} - onError={(e) => { - (e.target as HTMLImageElement).src = "/icons/white_without_bg.png" - }} - /> - ): type === "note" ? ( - <div className="shadow-md rounded-md bg-rgray-4 p-2 flex justify-center items-center"> - <Text className="w-10 h-10" /> - </div> - ) : ( - <></> - )} - </div> - </div> - ) + + <div className="flex h-24 w-24 items-center justify-center"> + {type === "page" ? ( + <img + className="h-16 w-16" + id={id.toString()} + src={image!} + onError={(e) => { + (e.target as HTMLImageElement).src = + "/icons/white_without_bg.png"; + }} + /> + ) : type === "note" ? ( + <div className="bg-rgray-4 flex items-center justify-center rounded-md p-2 shadow-md"> + <Text className="h-10 w-10" /> + </div> + ) : ( + <></> + )} + </div> + </div> + ); } export function SpaceItem({ @@ -236,8 +236,7 @@ export function SpaceItem({ onDelete, onClick, }: StoredSpace & { onDelete: () => void; onClick?: () => void }) { - - const { cachedMemories } = useMemory(); + const { cachedMemories } = useMemory(); const [itemRef, animateItem] = useAnimate(); const { width } = useViewport(); @@ -250,11 +249,11 @@ export function SpaceItem({ }, }); - const spaceMemories = useMemo(() => { - return cachedMemories.filter(m => m.space === id) - }, [cachedMemories]) + const spaceMemories = useMemo(() => { + return cachedMemories.filter((m) => m.space === id); + }, [cachedMemories]); - const _name = name.length > 10 ? name.slice(0, 10) + "..." : name + const _name = name.length > 10 ? name.slice(0, 10) + "..." : name; return ( <motion.div ref={itemRef} @@ -269,110 +268,120 @@ export function SpaceItem({ isOpen={moreDropdownOpen} setIsOpen={setMoreDropdownOpen} onDelete={() => { - onDelete() - return; + onDelete(); + return; if (!itemRef.current || width < 768) { onDelete(); return; } - // const trash = document.querySelector("#trash")! as HTMLDivElement; - // const trashBin = document.querySelector("#trash-button")!; - // const trashRect = trashBin.getBoundingClientRect(); - // const scopeRect = itemRef.current.getBoundingClientRect(); - // const el = document.createElement("div"); - // el.style.position = "fixed"; - // el.style.top = "0"; - // el.style.left = "0"; - // el.style.width = "15px"; - // el.style.height = "15px"; - // el.style.backgroundColor = "var(--gray-7)"; - // el.style.zIndex = "60"; - // el.style.borderRadius = "50%"; - // el.style.transform = "scale(5)"; - // el.style.opacity = "0"; - // trash.dataset["open"] = "true"; - // const initial = { - // x: scopeRect.left + scopeRect.width / 2, - // y: scopeRect.top + scopeRect.height / 2, - // }; - // const delta = { - // x: - // trashRect.left + - // trashRect.width / 2 - - // scopeRect.left + - // scopeRect.width / 2, - // y: - // trashRect.top + - // trashRect.height / 4 - - // scopeRect.top + - // scopeRect.height / 2, - // }; - // const end = { - // x: trashRect.left + trashRect.width / 2, - // y: trashRect.top + trashRect.height / 4, - // }; - // el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`; - // animateItem(itemRef.current, SpaceExitVariant, { - // duration: 0.2, - // }).then(() => { - // itemRef.current.style.scale = "0"; - // onDelete(); - // }); - // document.body.appendChild(el); - // el.animate( - // { - // transform: ["scale(5)", "scale(1)"], - // opacity: [0, 0.3, 1], - // }, - // { - // duration: 200, - // easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)", - // fill: "forwards", - // }, - // ); - // el.animate( - // { - // offsetDistance: ["0%", "100%"], - // }, - // { - // duration: 2000, - // easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)", - // fill: "forwards", - // delay: 200, - // }, - // ).onfinish = () => { - // el.animate( - // { transform: "scale(0)", opacity: 0 }, - // { duration: 200, fill: "forwards" }, - // ).onfinish = () => { - // el.remove(); - // }; - // }; + // const trash = document.querySelector("#trash")! as HTMLDivElement; + // const trashBin = document.querySelector("#trash-button")!; + // const trashRect = trashBin.getBoundingClientRect(); + // const scopeRect = itemRef.current.getBoundingClientRect(); + // const el = document.createElement("div"); + // el.style.position = "fixed"; + // el.style.top = "0"; + // el.style.left = "0"; + // el.style.width = "15px"; + // el.style.height = "15px"; + // el.style.backgroundColor = "var(--gray-7)"; + // el.style.zIndex = "60"; + // el.style.borderRadius = "50%"; + // el.style.transform = "scale(5)"; + // el.style.opacity = "0"; + // trash.dataset["open"] = "true"; + // const initial = { + // x: scopeRect.left + scopeRect.width / 2, + // y: scopeRect.top + scopeRect.height / 2, + // }; + // const delta = { + // x: + // trashRect.left + + // trashRect.width / 2 - + // scopeRect.left + + // scopeRect.width / 2, + // y: + // trashRect.top + + // trashRect.height / 4 - + // scopeRect.top + + // scopeRect.height / 2, + // }; + // const end = { + // x: trashRect.left + trashRect.width / 2, + // y: trashRect.top + trashRect.height / 4, + // }; + // el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`; + // animateItem(itemRef.current, SpaceExitVariant, { + // duration: 0.2, + // }).then(() => { + // itemRef.current.style.scale = "0"; + // onDelete(); + // }); + // document.body.appendChild(el); + // el.animate( + // { + // transform: ["scale(5)", "scale(1)"], + // opacity: [0, 0.3, 1], + // }, + // { + // duration: 200, + // easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)", + // fill: "forwards", + // }, + // ); + // el.animate( + // { + // offsetDistance: ["0%", "100%"], + // }, + // { + // duration: 2000, + // easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)", + // fill: "forwards", + // delay: 200, + // }, + // ).onfinish = () => { + // el.animate( + // { transform: "scale(0)", opacity: 0 }, + // { duration: 200, fill: "forwards" }, + // ).onfinish = () => { + // el.remove(); + // }; + // }; }} /> {spaceMemories.length > 2 ? ( <MemoryWithImages3 className="h-24 w-24" id={id.toString()} - images={spaceMemories.map((c) => c.type === 'note' ? '/note.svg' : c.image).reverse() as string[]} + images={ + spaceMemories + .map((c) => (c.type === "note" ? "/note.svg" : c.image)) + .reverse() as string[] + } /> ) : spaceMemories.length > 1 ? ( - <MemoryWithImages2 + <MemoryWithImages2 className="h-24 w-24" id={id.toString()} - images={spaceMemories.map((c) => c.type === 'note' ? '/note.svg' : c.image).reverse() as string[]} + images={ + spaceMemories + .map((c) => (c.type === "note" ? "/note.svg" : c.image)) + .reverse() as string[] + } /> - ) : spaceMemories.length === 1 ? ( + ) : spaceMemories.length === 1 ? ( <MemoryWithImage className="h-24 w-24" id={id.toString()} - image={spaceMemories[0].type === 'note' ? '/note.svg' : spaceMemories[0].image!} + image={ + spaceMemories[0].type === "note" + ? "/note.svg" + : spaceMemories[0].image! + } /> ) : ( - <div className="bg-rgray-4 opacity-30 rounded-full w-24 h-24 scale-50 shadow-"> - - </div> - )} + <div className="bg-rgray-4 shadow- h-24 w-24 scale-50 rounded-full opacity-30"></div> + )} </motion.div> ); } @@ -409,34 +418,32 @@ export function SpaceMoreButton({ <Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} /> Edit </DropdownMenuItem> - <DialogTrigger asChild> - <DropdownMenuItem - className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10" - > - <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} /> - Delete - </DropdownMenuItem> - </DialogTrigger> + <DialogTrigger asChild> + <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10"> + <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} /> + Delete + </DropdownMenuItem> + </DialogTrigger> </DropdownMenuContent> </DropdownMenu> - <DialogContent> - <DialogTitle className='text-xl'>Are you sure?</DialogTitle> - <DialogDescription className='text-md'>You will not be able to recover this space</DialogDescription> - <DialogFooter> - <DialogClose - type={undefined} - onClick={onDelete} - className="bg-red-500/40 focus-visible:bg-red-500/60 focus-visible:ring-red-500 hover:bg-red-500/60 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2" - > - Delete - </DialogClose> - <DialogClose - className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2" - > - Cancel - </DialogClose> - </DialogFooter> - </DialogContent> + <DialogContent> + <DialogTitle className="text-xl">Are you sure?</DialogTitle> + <DialogDescription className="text-md"> + You will not be able to recover this space + </DialogDescription> + <DialogFooter> + <DialogClose + type={undefined} + onClick={onDelete} + className="ml-auto flex items-center justify-center rounded-md bg-red-500/40 px-3 py-2 transition hover:bg-red-500/60 focus-visible:bg-red-500/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500" + > + Delete + </DialogClose> + <DialogClose className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2"> + Cancel + </DialogClose> + </DialogFooter> + </DialogContent> </Dialog> ); } diff --git a/apps/web/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx index f3534b55..afc2cf46 100644 --- a/apps/web/src/components/ui/command.tsx +++ b/apps/web/src/components/ui/command.tsx @@ -7,7 +7,6 @@ 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<typeof CommandPrimitive>, @@ -40,13 +39,19 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => { const CommandInput = React.forwardRef< React.ElementRef<typeof CommandPrimitive.Input>, - React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & { isSearching?: boolean } ->(({ className, isSearching = false ,...props }, ref) => ( + React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & { + isSearching?: boolean; + } +>(({ className, isSearching = false, ...props }, ref) => ( <div className="border-rgray-6 flex items-center border-b px-3" cmdk-input-wrapper="" > - {isSearching ? <Loader className="mr-2 h-4 w-4 shrink-9 opacity-50 animate-spin" /> : <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />} + {isSearching ? ( + <Loader className="shrink-9 mr-2 h-4 w-4 animate-spin opacity-50" /> + ) : ( + <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" /> + )} <CommandPrimitive.Input ref={ref} className={cn( |