diff options
| author | Dhravya <[email protected]> | 2024-05-21 22:47:33 -0500 |
|---|---|---|
| committer | Dhravya <[email protected]> | 2024-05-21 22:47:33 -0500 |
| commit | a4f566aaa085ce2a871f12070f932fa7da557316 (patch) | |
| tree | e8ff7e3eb0a27ef47300f261975afdefae093b32 /apps/web/src/components/Sidebar | |
| parent | footer fix: Arrow color should be consistent, no need for stroke (diff) | |
| download | supermemory-a4f566aaa085ce2a871f12070f932fa7da557316.tar.xz supermemory-a4f566aaa085ce2a871f12070f932fa7da557316.zip | |
format documents
Diffstat (limited to 'apps/web/src/components/Sidebar')
| -rw-r--r-- | apps/web/src/components/Sidebar/DeleteConfirmation.tsx | 48 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/ExpandedSpace.tsx | 325 |
2 files changed, 216 insertions, 157 deletions
diff --git a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx index 9324b147..7955df0d 100644 --- a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx +++ b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx @@ -1,18 +1,30 @@ -import { Dialog, DialogContent, DialogTrigger, DialogTitle, DialogDescription, DialogClose, DialogFooter } from "../ui/dialog"; +import { + Dialog, + DialogContent, + DialogTrigger, + DialogTitle, + DialogDescription, + DialogClose, + DialogFooter, +} from "../ui/dialog"; -export default function DeleteConfirmation({ onDelete, trigger = true, children }: { trigger?: boolean, onDelete?: () => void; children: React.ReactNode }) { - return ( - <Dialog> - {trigger ? ( - <DialogTrigger asChild> - {children} - </DialogTrigger> - ) : ( - <> - {children} - </> - )} - <DialogContent> +export default function DeleteConfirmation({ + onDelete, + trigger = true, + children, +}: { + trigger?: boolean; + onDelete?: () => void; + children: React.ReactNode; +}) { + return ( + <Dialog> + {trigger ? ( + <DialogTrigger asChild>{children}</DialogTrigger> + ) : ( + <>{children}</> + )} + <DialogContent> <DialogTitle className="text-xl">Are you sure?</DialogTitle> <DialogDescription className="text-md"> You will not be able to recover this it. @@ -21,7 +33,7 @@ export default function DeleteConfirmation({ onDelete, trigger = true, children <DialogClose type={undefined} onClick={onDelete} - className="ml-auto flex items-center justify-center rounded-md text-red-400 bg-red-100/10 px-3 py-2 transition hover:bg-red-100/5 focus-visible:bg-red-100/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-100/30" + className="ml-auto flex items-center justify-center rounded-md bg-red-100/10 px-3 py-2 text-red-400 transition hover:bg-red-100/5 focus-visible:bg-red-100/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-100/30" > Delete </DialogClose> @@ -29,7 +41,7 @@ export default function DeleteConfirmation({ onDelete, trigger = true, children Cancel </DialogClose> </DialogFooter> - </DialogContent> - </Dialog> - ) + </DialogContent> + </Dialog> + ); } diff --git a/apps/web/src/components/Sidebar/ExpandedSpace.tsx b/apps/web/src/components/Sidebar/ExpandedSpace.tsx index 9e46f3fb..55d3f3f8 100644 --- a/apps/web/src/components/Sidebar/ExpandedSpace.tsx +++ b/apps/web/src/components/Sidebar/ExpandedSpace.tsx @@ -1,33 +1,52 @@ -import { fetchContentForSpace, getSpace } from "@/actions/db" -import { useMemory } from "@/contexts/MemoryContext" -import { StoredContent, StoredSpace } from '@/server/db/schema' -import { Edit3, Loader, Plus, Search, Sparkles, StickyNote, Text, Undo2 } from "lucide-react" -import { useEffect, useRef, useState } from "react" -import { Input, InputWithIcon } from "../ui/input" -import { useDebounce } from "@/hooks/useDebounce" -import { useAutoAnimate } from "@formkit/auto-animate/react" -import { AddMemoryModal, MemoryItem } from "./MemoriesBar" -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu" -import { DialogTrigger } from "../ui/dialog" - -export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => void; }) { - - const { updateMemory, updateSpace, search } = useMemory() +import { fetchContentForSpace, getSpace } from "@/actions/db"; +import { useMemory } from "@/contexts/MemoryContext"; +import { StoredContent, StoredSpace } from "@/server/db/schema"; +import { + Edit3, + Loader, + Plus, + Search, + Sparkles, + StickyNote, + Text, + Undo2, +} from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { Input, InputWithIcon } from "../ui/input"; +import { useDebounce } from "@/hooks/useDebounce"; +import { useAutoAnimate } from "@formkit/auto-animate/react"; +import { AddMemoryModal, MemoryItem } from "./MemoriesBar"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; +import { DialogTrigger } from "../ui/dialog"; + +export function ExpandedSpace({ + spaceId, + back, +}: { + spaceId: number; + back: () => void; +}) { + const { updateMemory, updateSpace, search } = useMemory(); const [parent, enableAnimations] = useAutoAnimate(); - const inputRef = useRef<HTMLInputElement>(null); + const inputRef = useRef<HTMLInputElement>(null); - const [contentForSpace, setContentForSpace] = useState<StoredContent[]>([]) + const [contentForSpace, setContentForSpace] = useState<StoredContent[]>([]); - const [lastUpdatedTitle, setLastUpdatedTitle] = useState<string | null>(null); + const [lastUpdatedTitle, setLastUpdatedTitle] = useState<string | null>(null); - const [title, setTitle] = useState<string>(''); - const debouncedTitle = useDebounce(title, 500) + const [title, setTitle] = useState<string>(""); + const debouncedTitle = useDebounce(title, 500); - const [loading, setLoading] = useState(true) + const [loading, setLoading] = useState(true); - const [saveLoading, setSaveLoading] = useState(false); + const [saveLoading, setSaveLoading] = useState(false); const [searchQuery, setSearcyQuery] = useState(""); const [searchLoading, setSearchLoading] = useState(false); @@ -35,33 +54,34 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => const [searchResults, setSearchResults] = useState<StoredContent[]>([]); - const [addMemoryState, setAddMemoryState] = useState< "page" | "note" | "existing-memory" | "space" | null >(null); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - - - useEffect(() => { - (async () => { - const title = (await getSpace(spaceId))?.name ?? ""; - setTitle(title) - setLastUpdatedTitle(title) - setContentForSpace((await fetchContentForSpace(spaceId)) ?? []) - setLoading(false) - })(); - }, []) - - useEffect(() => { - if (debouncedTitle.trim().length < 1 || debouncedTitle.trim() === lastUpdatedTitle?.trim()) return - (async () => { - setSaveLoading(true) - await updateSpace(spaceId, debouncedTitle.trim()) - setLastUpdatedTitle(debouncedTitle) - setSaveLoading(false) - })() - }, [debouncedTitle]) + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + useEffect(() => { + (async () => { + const title = (await getSpace(spaceId))?.name ?? ""; + setTitle(title); + setLastUpdatedTitle(title); + setContentForSpace((await fetchContentForSpace(spaceId)) ?? []); + setLoading(false); + })(); + }, []); + useEffect(() => { + if ( + debouncedTitle.trim().length < 1 || + debouncedTitle.trim() === lastUpdatedTitle?.trim() + ) + return; + (async () => { + setSaveLoading(true); + await updateSpace(spaceId, debouncedTitle.trim()); + setLastUpdatedTitle(debouncedTitle); + setSaveLoading(false); + })(); + }, [debouncedTitle]); useEffect(() => { const q = query.trim(); @@ -73,56 +93,68 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => setSearchLoading(true); (async () => { - setSearchResults((await search(q, { - filter: { spaces: false }, - memoriesRelativeToSpace: { - fromSpaces: [spaceId] - } - })).map(i => i.memory!)); + setSearchResults( + ( + await search(q, { + filter: { spaces: false }, + memoriesRelativeToSpace: { + fromSpaces: [spaceId], + }, + }) + ).map((i) => i.memory!), + ); setSearchLoading(false); })(); }, [query]); - - if (loading) { - return ( - <div className="h-full w-full flex justify-center items-center"> - <Loader className="w-5 h-5 animate-spin" /> - </div> - ) - } + if (loading) { + return ( + <div className="flex h-full w-full items-center justify-center"> + <Loader className="h-5 w-5 animate-spin" /> + </div> + ); + } return ( <div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left"> - <div className="px-8 flex justify-start items-center w-full gap-2"> - <button onClick={back} className="transition rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7"> - <Undo2 className="w-5 h-5" /> - </button> - <Input - ref={inputRef} - data-error="false" - className="w-full border-none p-0 text-xl ring-0 placeholder:text-white/30 placeholder:transition placeholder:duration-500 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400" - placeholder="Title of the space" - data-modal-autofocus - value={title} - onChange={(e) => setTitle(e.target.value)} - /> - <button onClick={() => { - inputRef.current?.focus() - inputRef.current?.animate({ - opacity: [1, 0.2, 1] - }, { - duration: 100 - }) - }} className="transition rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7"> - {saveLoading ? ( - <Loader className="w-5 h-5 opacity-70 animate-spin" /> - ) : ( - <Edit3 className="w-5 h-5 opacity-70" /> - )} - </button> - </div> - <div className="px-8 w-full"> + <div className="flex w-full items-center justify-start gap-2 px-8"> + <button + onClick={back} + className="focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7 rounded-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2" + > + <Undo2 className="h-5 w-5" /> + </button> + <Input + ref={inputRef} + data-error="false" + className="w-full border-none p-0 text-xl ring-0 placeholder:text-white/30 placeholder:transition placeholder:duration-500 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400" + placeholder="Title of the space" + data-modal-autofocus + value={title} + onChange={(e) => setTitle(e.target.value)} + /> + <button + onClick={() => { + inputRef.current?.focus(); + inputRef.current?.animate( + { + opacity: [1, 0.2, 1], + }, + { + duration: 100, + }, + ); + }} + className="focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7 rounded-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2" + > + {saveLoading ? ( + <Loader className="h-5 w-5 animate-spin opacity-70" /> + ) : ( + <Edit3 className="h-5 w-5 opacity-70" /> + )} + </button> + </div> + <div className="w-full px-8"> <InputWithIcon placeholder="Search" icon={ @@ -136,23 +168,28 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => value={searchQuery} onChange={(e) => setSearcyQuery(e.target.value)} /> - </div> - <div className="w-full px-8 mt-2"> - <AddMemoryModal onAdd={(data) => { - if (!data) { - setLoading(true); - (async () => { - const title = (await getSpace(spaceId))?.name ?? ""; - setTitle(title) - setLastUpdatedTitle(title) - setContentForSpace((await fetchContentForSpace(spaceId)) ?? []) - setLoading(false) - })(); - } else if (Object.hasOwn(data, "url")) { - const _data = data as StoredContent; - setContentForSpace(prev => [...prev, _data]) - } - }} data={{ space: { title, id: spaceId }, notInSpaces: [spaceId] }} defaultSpaces={[spaceId]} type={addMemoryState}> + </div> + <div className="mt-2 w-full px-8"> + <AddMemoryModal + onAdd={(data) => { + if (!data) { + setLoading(true); + (async () => { + const title = (await getSpace(spaceId))?.name ?? ""; + setTitle(title); + setLastUpdatedTitle(title); + setContentForSpace((await fetchContentForSpace(spaceId)) ?? []); + setLoading(false); + })(); + } else if (Object.hasOwn(data, "url")) { + const _data = data as StoredContent; + setContentForSpace((prev) => [...prev, _data]); + } + }} + data={{ space: { title, id: spaceId }, notInSpaces: [spaceId] }} + defaultSpaces={[spaceId]} + type={addMemoryState} + > <DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}> <DropdownMenuTrigger asChild> <button 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"> @@ -194,47 +231,57 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => </DropdownMenuContent> </DropdownMenu> </AddMemoryModal> - </div> + </div> <div ref={parent} className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5" > - {query.trim().length > 0 ? ( + {query.trim().length > 0 ? ( <> {searchResults.map((memory, i) => ( - <MemoryItem - removeFromSpace={async () => { - await updateMemory(memory.id, { - removedFromSpaces: [spaceId] - }) - setContentForSpace(prev => prev.filter(s => s.id !== memory.id)) - setSearchResults(prev => prev.filter(i => i.id !== memory.id)) - }} - {...memory!} - key={i} - onDelete={() => { - setContentForSpace(prev => prev.filter(s => s.id !== memory.id)) - setSearchResults(prev => prev.filter(i => i.id !== memory.id)) - }} - /> + <MemoryItem + removeFromSpace={async () => { + await updateMemory(memory.id, { + removedFromSpaces: [spaceId], + }); + setContentForSpace((prev) => + prev.filter((s) => s.id !== memory.id), + ); + setSearchResults((prev) => + prev.filter((i) => i.id !== memory.id), + ); + }} + {...memory!} + key={i} + onDelete={() => { + setContentForSpace((prev) => + prev.filter((s) => s.id !== memory.id), + ); + setSearchResults((prev) => + prev.filter((i) => i.id !== memory.id), + ); + }} + /> ))} </> - ) : - contentForSpace.map(m => ( - <MemoryItem - key={m.id} - {...m} - onDelete={() => setContentForSpace(prev => prev.filter(s => s.id !== m.id))} - removeFromSpace={async () => { - await updateMemory(m.id, { - removedFromSpaces: [spaceId] - }) - setContentForSpace(prev => prev.filter(s => s.id !== m.id)) - }} - /> - )) - } - </div> + ) : ( + contentForSpace.map((m) => ( + <MemoryItem + key={m.id} + {...m} + onDelete={() => + setContentForSpace((prev) => prev.filter((s) => s.id !== m.id)) + } + removeFromSpace={async () => { + await updateMemory(m.id, { + removedFromSpaces: [spaceId], + }); + setContentForSpace((prev) => prev.filter((s) => s.id !== m.id)); + }} + /> + )) + )} + </div> </div> - ) + ); } |