aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
authorYash <[email protected]>2024-04-11 11:02:24 +0000
committerYash <[email protected]>2024-04-11 11:02:24 +0000
commitbf99ee97f7c4d7580829d074816ebe0d32316d92 (patch)
tree6bf59543316a15399ae3cc001b7cdd0fc2c00d39 /apps/web/src/components
parentschema update (diff)
downloadsupermemory-bf99ee97f7c4d7580829d074816ebe0d32316d92.tar.xz
supermemory-bf99ee97f7c4d7580829d074816ebe0d32316d92.zip
ok
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/Main.tsx6
-rw-r--r--apps/web/src/components/Sidebar/AddMemoryDialog.tsx100
-rw-r--r--apps/web/src/components/Sidebar/FilterCombobox.tsx119
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx18
4 files changed, 187 insertions, 56 deletions
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
index 0235d608..c7bb3f1e 100644
--- a/apps/web/src/components/Main.tsx
+++ b/apps/web/src/components/Main.tsx
@@ -1,6 +1,6 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
-import { FilterCombobox } from "./Sidebar/FilterCombobox";
+import { FilterSpaces } from "./Sidebar/FilterCombobox";
import { Textarea2 } from "./ui/textarea";
import { ArrowRight, ArrowUp } from "lucide-react";
import { MemoryDrawer } from "./MemoryDrawer";
@@ -307,7 +307,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
}}
>
<div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
- <FilterCombobox
+ <FilterSpaces
name={"Filter"}
onClose={() => {
textArea.current?.querySelector("textarea")?.focus();
@@ -383,7 +383,7 @@ export function Chat({
className="absolute flex w-full items-center justify-center"
>
<div className="animate-from-top fixed bottom-10 mt-auto flex w-[50%] flex-col items-start justify-center gap-2">
- <FilterCombobox
+ <FilterSpaces
name={"Filter"}
onClose={() => {
textArea.current?.querySelector("textarea")?.focus();
diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
index aa86966f..886507ff 100644
--- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
+++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
@@ -10,13 +10,17 @@ import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { Markdown } from "tiptap-markdown";
import { useEffect, useRef, useState } from "react";
-import { FilterCombobox } from "./FilterCombobox";
+import { FilterSpaces } from "./FilterCombobox";
+import { useMemory } from "@/contexts/MemoryContext";
export function AddMemoryPage() {
+ const { addMemory } = useMemory();
+
+ const [url, setUrl] = useState("");
const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>([]);
return (
- <div className="md:w-[40vw]">
+ <form className="md:w-[40vw]">
<DialogHeader>
<DialogTitle>Add a web page to memory</DialogTitle>
<DialogDescription>
@@ -30,25 +34,41 @@ export function AddMemoryPage() {
type="url"
data-modal-autofocus
className="bg-rgray-4 mt-2 w-full"
+ value={url}
+ onChange={(e) => setUrl(e.target.value)}
/>
<DialogFooter>
- <FilterCombobox
+ <FilterSpaces
selectedSpaces={selectedSpacesId}
setSelectedSpaces={setSelectedSpacesId}
className="hover:bg-rgray-5 mr-auto bg-white/5"
name={"Spaces"}
/>
- <DialogClose
- type={undefined}
+ <button
+ type={"submit"}
+ onClick={async () => {
+ // @Dhravya this is adding a memory with insufficient information fix pls
+ await addMemory(
+ {
+ title: url,
+ content: "",
+ type: "page",
+ url: url,
+ image: "/icons/logo_without_bg.png",
+ savedAt: new Date(),
+ },
+ selectedSpacesId,
+ );
+ }}
className="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"
>
Add
- </DialogClose>
+ </button>
<DialogClose 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">
Cancel
</DialogClose>
</DialogFooter>
- </div>
+ </form>
);
}
@@ -60,16 +80,15 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
const [content, setContent] = useState("");
const [loading, setLoading] = useState(false);
- function check() {
+ 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;
+ return false;
}
inputRef.current.value = "";
inputRef.current.placeholder = "Please enter a title for the note";
@@ -79,7 +98,9 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
inputRef.current!.dataset["error"] = "false";
}, 500);
inputRef.current.focus();
+ return false;
}
+ return true;
}
return (
@@ -87,7 +108,7 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
<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-200 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400"
+ 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 note"
data-modal-autofocus
value={name}
@@ -104,7 +125,7 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
className="novel-editor bg-rgray-4 border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] w-[50vw] overflow-y-auto rounded-lg border [&>div>div]:p-5"
/>
<DialogFooter>
- <FilterCombobox
+ <FilterSpaces
selectedSpaces={selectedSpacesId}
setSelectedSpaces={setSelectedSpacesId}
className="hover:bg-rgray-5 mr-auto bg-white/5"
@@ -112,7 +133,9 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
/>
<button
onClick={() => {
- check();
+ if (check()) {
+ closeDialog();
+ }
}}
className="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"
>
@@ -137,7 +160,7 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
const [content, setContent] = useState("");
const [loading, setLoading] = useState(false);
- function check() {
+ function check(): boolean {
const data = {
name: name.trim(),
content,
@@ -146,7 +169,7 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
if (!data.name || data.name.length < 1) {
if (!inputRef.current) {
alert("Please enter a name for the note");
- return;
+ return false;
}
inputRef.current.value = "";
inputRef.current.placeholder = "Please enter a title for the note";
@@ -156,49 +179,32 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
inputRef.current!.dataset["error"] = "false";
}, 500);
inputRef.current.focus();
+ return false;
}
+ return true;
}
return (
- <div>
+ <div className="md:w-[40vw]">
+ <DialogHeader>
+ <DialogTitle>Add a space</DialogTitle>
+ </DialogHeader>
+ <Label className="mt-5 block">Name</Label>
<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-200 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400"
- placeholder="Title of the note"
+ placeholder="Enter the name of the space"
+ type="url"
data-modal-autofocus
- value={name}
- onChange={(e) => setName(e.target.value)}
- />
- <Editor
- disableLocalStorage
- defaultValue={""}
- onUpdate={(editor) => {
- if (!editor) return;
- setContent(editor.storage.markdown.getMarkdown());
- }}
- extensions={[Markdown]}
- className="novel-editor bg-rgray-4 border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] w-[50vw] overflow-y-auto rounded-lg border [&>div>div]:p-5"
+ className="bg-rgray-4 mt-2 w-full"
/>
+ <Label className="mt-5 block">Memories</Label>
<DialogFooter>
- <FilterCombobox
- selectedSpaces={selectedSpacesId}
- setSelectedSpaces={setSelectedSpacesId}
- className="hover:bg-rgray-5 mr-auto bg-white/5"
- name={"Spaces"}
- />
- <button
- onClick={() => {
- check();
- }}
- className="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"
- >
- Add
- </button>
<DialogClose
type={undefined}
- 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"
+ className="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"
>
+ Add
+ </DialogClose>
+ <DialogClose 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">
Cancel
</DialogClose>
</DialogFooter>
diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx
index 04ff0324..0a93ee55 100644
--- a/apps/web/src/components/Sidebar/FilterCombobox.tsx
+++ b/apps/web/src/components/Sidebar/FilterCombobox.tsx
@@ -33,7 +33,124 @@ export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
name: string;
}
-export function FilterCombobox({
+export function FilterSpaces({
+ className,
+ side = "bottom",
+ align = "center",
+ onClose,
+ selectedSpaces,
+ setSelectedSpaces,
+ name,
+ ...props
+}: Props) {
+ const { spaces } = useMemory();
+ const [open, setOpen] = React.useState(false);
+
+ const sortedSpaces = spaces.sort(({ id: a }, { id: b }) =>
+ selectedSpaces.includes(a) && !selectedSpaces.includes(b)
+ ? -1
+ : selectedSpaces.includes(b) && !selectedSpaces.includes(a)
+ ? 1
+ : 0,
+ );
+
+ React.useEffect(() => {
+ if (!open) {
+ onClose?.();
+ }
+ }, [open]);
+
+ return (
+ <AnimatePresence mode="popLayout">
+ <LayoutGroup>
+ <Popover open={open} onOpenChange={setOpen}>
+ <PopoverTrigger asChild>
+ <button
+ type={undefined}
+ data-state-on={open}
+ className={cn(
+ "text-rgray-11/70 on:bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-3 relative flex items-center justify-center gap-1 rounded-md px-3 py-1.5 ring-2 ring-transparent focus-visible:outline-none",
+ className,
+ )}
+ {...props}
+ >
+ <SpaceIcon className="mr-1 h-5 w-5" />
+ {name}
+ <ChevronsUpDown className="h-4 w-4" />
+ <div
+ data-state-on={selectedSpaces.length > 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}
+ </div>
+ </button>
+ </PopoverTrigger>
+ <PopoverContent
+ onCloseAutoFocus={(e) => e.preventDefault()}
+ align={align}
+ side={side}
+ className="w-[200px] p-0"
+ >
+ <Command
+ filter={(val, search) =>
+ spaces
+ .find((s) => s.id.toString() === val)
+ ?.title.toLowerCase()
+ .includes(search.toLowerCase().trim())
+ ? 1
+ : 0
+ }
+ >
+ <CommandInput placeholder="Filter spaces..." />
+ <CommandList asChild>
+ <motion.div layoutScroll>
+ <CommandEmpty>Nothing found</CommandEmpty>
+ <CommandGroup>
+ {sortedSpaces.map((space) => (
+ <CommandItem
+ key={space.id}
+ value={space.id.toString()}
+ onSelect={(val) => {
+ setSelectedSpaces((prev: number[]) =>
+ prev.includes(parseInt(val))
+ ? prev.filter((v) => v !== parseInt(val))
+ : [...prev, parseInt(val)],
+ );
+ }}
+ asChild
+ >
+ <motion.div
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1, transition: { delay: 0.05 } }}
+ transition={{ duration: 0.15 }}
+ layout
+ layoutId={`space-combobox-${space.id}`}
+ className="text-rgray-11"
+ >
+ <SpaceIcon className="mr-2 h-4 w-4" />
+ {space.title}
+ {selectedSpaces.includes(space.id)}
+ <Check
+ data-state-on={selectedSpaces.includes(space.id)}
+ className={cn(
+ "on:opacity-100 ml-auto h-4 w-4 opacity-0",
+ )}
+ />
+ </motion.div>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </motion.div>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ </LayoutGroup>
+ </AnimatePresence>
+ );
+}
+
+export function FilterMemories({
className,
side = "bottom",
align = "center",
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
index 3d7bd8b9..66c3138b 100644
--- a/apps/web/src/components/Sidebar/MemoriesBar.tsx
+++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx
@@ -40,7 +40,7 @@ import { Label } from "../ui/label";
import useViewport from "@/hooks/useViewport";
import useTouchHold from "@/hooks/useTouchHold";
import { DialogTrigger } from "@radix-ui/react-dialog";
-import { AddMemoryPage, NoteAddPage } from "./AddMemoryDialog";
+import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog";
export function MemoriesBar() {
const [parent, enableAnimations] = useAutoAnimate();
@@ -91,10 +91,16 @@ export function MemoriesBar() {
Note
</DropdownMenuItem>
</DialogTrigger>
- <DropdownMenuItem>
- <SpaceIcon className="mr-2 h-4 w-4" />
- Space
- </DropdownMenuItem>
+ <DialogTrigger className="block w-full">
+ <DropdownMenuItem
+ onClick={() => {
+ setAddMemoryState("space");
+ }}
+ >
+ <SpaceIcon className="mr-2 h-4 w-4" />
+ Space
+ </DropdownMenuItem>
+ </DialogTrigger>
</DropdownMenuContent>
</DropdownMenu>
</AddMemoryModal>
@@ -343,6 +349,8 @@ export function AddMemoryModal({
<AddMemoryPage />
) : type === "note" ? (
<NoteAddPage closeDialog={() => setIsDialogOpen(false)} />
+ ) : type === "space" ? (
+ <SpaceAddPage closeDialog={() => setIsDialogOpen(false)} />
) : (
<></>
)}