aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src/components/Sidebar
diff options
context:
space:
mode:
authoryxshv <[email protected]>2024-04-13 18:11:14 +0530
committeryxshv <[email protected]>2024-04-13 18:11:14 +0530
commit5b340071245cfe9906fd3534ec5176de1e3fd3bd (patch)
treebf4860f810f58adcfec4cc0ad3fdcc3da62bda95 /apps/web/src/components/Sidebar
parentsearch results (diff)
downloadsupermemory-5b340071245cfe9906fd3534ec5176de1e3fd3bd.tar.xz
supermemory-5b340071245cfe9906fd3534ec5176de1e3fd3bd.zip
spaces dialog
Diffstat (limited to 'apps/web/src/components/Sidebar')
-rw-r--r--apps/web/src/components/Sidebar/AddMemoryDialog.tsx66
-rw-r--r--apps/web/src/components/Sidebar/FilterCombobox.tsx159
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx4
3 files changed, 143 insertions, 86 deletions
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<number[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
const [name, setName] = useState("");
- const [content, setContent] = useState("");
const [loading, setLoading] = useState(false);
+ const [selected, setSelected] = useState<StoredContent[]>([]);
+
+
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 }) {
</DialogHeader>
<Label className="mt-5 block">Name</Label>
<Input
+ ref={inputRef}
placeholder="Enter the name of the space"
type="url"
data-modal-autofocus
- className="bg-rgray-4 mt-2 w-full"
+ value={name}
+ 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"
/>
- <Label className="mt-5 block">Memories</Label>
+ {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>
+ </>
+ )}
<DialogFooter>
- <DialogClose
+ <FilterMemories
+ selected={selected}
+ setSelected={setSelected}
+ className="mr-auto bg-white/5 hover:bg-rgray-4 focus-visible:bg-rgray-4"
+ >
+ <Plus className="w-5 h-5" />
+ Memory
+ </FilterMemories>
+ <button
type={undefined}
+ onClick={() => {
+ if (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
- </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>
@@ -211,3 +242,16 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
</div>
);
}
+
+export function MemorySelectedItem({ id, title, url, 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={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">{cleanUrl(url)}</span>
+ </div>
+ )
+}
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<HTMLButtonElement> {
+export interface FilterSpacesProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
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.SetStateAction<StoredContent[]>>;
+} & React.ButtonHTMLAttributes<HTMLButtonElement>
+
export function FilterMemories({
className,
side = "bottom",
align = "center",
onClose,
- selectedSpaces,
- setSelectedSpaces,
- name,
+ selected,
+ setSelected,
...props
-}: Props) {
- const { spaces } = useMemory();
+}: FilterMemoriesProps) {
+
+ const { search } = useMemory();
+
const [open, setOpen] = React.useState(false);
+ const [searchQuery, setSearchQuery] = React.useState("");
+ const query = useDebounce(searchQuery, 500)
- const sortedSpaces = spaces.sort(({ id: a }, { id: b }) =>
- selectedSpaces.includes(a) && !selectedSpaces.includes(b)
- ? -1
- : selectedSpaces.includes(b) && !selectedSpaces.includes(a)
- ? 1
- : 0,
- );
+ const [searchResults, setSearchResults] = React.useState<SearchResult[]>([]);
+ 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 (
<AnimatePresence mode="popLayout">
<LayoutGroup>
@@ -191,15 +226,7 @@ export function FilterMemories({
)}
{...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>
+ {props.children}
</button>
</PopoverTrigger>
<PopoverContent
@@ -209,56 +236,42 @@ export function FilterMemories({
className="w-[200px] p-0"
>
<Command
- filter={(val, search) =>
- spaces
- .find((s) => s.id.toString() === val)
- ?.name.toLowerCase()
- .includes(search.toLowerCase().trim())
- ? 1
- : 0
- }
+ shouldFilter={false}
>
- <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.name}
- {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>
+ <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.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 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<number | null>(null);
const [searchQuery, setSearcyQuery] = useState("");
const [searchLoading, setSearchLoading] = useState(false)
- const query = useDebounce(searchQuery, 1000)
+ const query = useDebounce(searchQuery, 500)
const [searchResults, setSearchResults] = useState<SearchResult[]>([])
@@ -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) => (
<>