aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-04-13 09:14:37 -0700
committerDhravya <[email protected]>2024-04-13 09:14:37 -0700
commit75cae41f2aebd6343ce95cd0b6910870724ed374 (patch)
tree44c9a92202801aea87cdaffd51bcd8b836fa69ea /apps
parentCloudflareAI for better streaming (diff)
downloadsupermemory-75cae41f2aebd6343ce95cd0b6910870724ed374.tar.xz
supermemory-75cae41f2aebd6343ce95cd0b6910870724ed374.zip
merge
Diffstat (limited to 'apps')
-rw-r--r--apps/extension/package.json1
-rw-r--r--apps/extension/src/SideBar.tsx175
-rw-r--r--apps/extension/src/background.ts46
-rw-r--r--apps/extension/src/components/FilterCombobox.tsx175
-rw-r--r--apps/extension/src/components/ui/dropdown-menu.tsx204
-rw-r--r--apps/web/src/actions/db.ts64
-rw-r--r--apps/web/src/app/page.tsx39
-rw-r--r--apps/web/src/components/Sidebar/AddMemoryDialog.tsx31
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx232
-rw-r--r--apps/web/src/contexts/MemoryContext.tsx80
-rw-r--r--apps/web/src/server/db/schema.ts5
-rw-r--r--apps/web/src/server/db/test.ts6
-rw-r--r--apps/web/types/memory.tsx58
13 files changed, 677 insertions, 439 deletions
diff --git a/apps/extension/package.json b/apps/extension/package.json
index 4c12ef20..a7f34ea6 100644
--- a/apps/extension/package.json
+++ b/apps/extension/package.json
@@ -12,6 +12,7 @@
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
+ "@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-tooltip": "^1.0.7",
"cmdk": "^1.0.0",
diff --git a/apps/extension/src/SideBar.tsx b/apps/extension/src/SideBar.tsx
index 34992386..9704511b 100644
--- a/apps/extension/src/SideBar.tsx
+++ b/apps/extension/src/SideBar.tsx
@@ -7,19 +7,20 @@ import {
TooltipProvider,
TooltipTrigger,
} from "./components/ui/tooltip";
-// import { FilterSpaces } from "./components/FilterCombobox";
-// import {
-// Dialog,
-// DialogContent,
-// DialogHeader,
-// DialogTitle,
-// DialogDescription,
-// DialogTrigger,
-// DialogFooter,
-// DialogClose,
-// } from "./components/ui/dialog";
+import { FilterSpaces } from "./components/FilterCombobox";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogTrigger,
+ DialogFooter,
+ DialogClose,
+} from "./components/ui/dialog";
+import { Space } from "./types/memory";
-function sendUrlToAPI() {
+function sendUrlToAPI(spaces: number[]) {
// get the current URL
const url = window.location.href;
@@ -31,7 +32,7 @@ function sendUrlToAPI() {
} else {
// const content = Entire page content, but cleaned up for the LLM. No ads, no scripts, no styles, just the text. if article, just the importnat info abou tit.
const content = document.documentElement.innerText;
- chrome.runtime.sendMessage({ type: "urlChange", content, url });
+ chrome.runtime.sendMessage({ type: "urlChange", content, url, spaces });
}
}
@@ -50,7 +51,9 @@ function SideBar({ jwt }: { jwt: string }) {
const [isSendingData, setIsSendingData] = useState(false);
- // const [selectedSpaces, setSelectedSpaces] = useState<number[]>([0, 1]);
+ const [loading, setLoading] = useState(false);
+ const [spaces, setSpaces] = useState<Space[]>();
+ const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
const [isImportingTweets, setIsImportingTweets] = useState(false);
@@ -73,6 +76,15 @@ function SideBar({ jwt }: { jwt: string }) {
});
}
+ const fetchSpaces = async () => {
+ setLoading(true);
+ chrome.runtime.sendMessage({ type: "fetchSpaces" }, (resp) => {
+ console.log(resp);
+ setSpaces(resp);
+ setLoading(false);
+ });
+ };
+
const fetchBookmarks = () => {
const tweets: TweetData[] = []; // Initialize an empty array to hold all tweet elements
@@ -253,67 +265,58 @@ function SideBar({ jwt }: { jwt: string }) {
) : (
<></>
)}
- {/* <Dialog> */}
- <Tooltip delayDuration={300}>
- <TooltipTrigger
- className="anycontext-bg-transparent
+ <Dialog onOpenChange={(open) => open === true && fetchSpaces()}>
+ <Tooltip delayDuration={300}>
+ <TooltipTrigger
+ className="anycontext-bg-transparent
anycontext-border-none anycontext-m-0 anycontext-p-0
"
- >
- {/* <DialogTrigger asChild> */}
- <button
- onClick={() => {
- // return;
- sendUrlToAPI();
- setIsSendingData(true);
- setTimeout(() => {
- setIsSendingData(false);
- setSavedWebsites([...savedWebsites, window.location.href]);
- }, 1000);
- }}
- disabled={savedWebsites.includes(window.location.href)}
- className="anycontext-open-button disabled:anycontext-opacity-30 anycontext-bg-transparent
- anycontext-border-none anycontext-m-0 anycontext-p-0"
>
- {savedWebsites.includes(window.location.href) ? (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="24"
- height="24"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- strokeWidth="2"
- strokeLinecap="round"
- strokeLinejoin="round"
- className="lucide lucide-file-check-2"
- >
- <path d="M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" />
- <path d="M14 2v4a2 2 0 0 0 2 2h4" />
- <path d="m3 15 2 2 4-4" />
- </svg>
- ) : (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- className={`anycontext-w-5 anycontext-h-5 ${isSendingData ? "anycontext-animate-spin" : ""}`}
+ <DialogTrigger asChild>
+ <button
+ disabled={savedWebsites.includes(window.location.href)}
+ className="anycontext-open-button disabled:anycontext-opacity-30 anycontext-bg-transparent
+ anycontext-border-none anycontext-m-0 anycontext-p-0"
>
- <path d="M15.98 1.804a1 1 0 0 0-1.96 0l-.24 1.192a1 1 0 0 1-.784.785l-1.192.238a1 1 0 0 0 0 1.962l1.192.238a1 1 0 0 1 .785.785l.238 1.192a1 1 0 0 0 1.962 0l.238-1.192a1 1 0 0 1 .785-.785l1.192-.238a1 1 0 0 0 0-1.962l-1.192-.238a1 1 0 0 1-.785-.785l-.238-1.192ZM6.949 5.684a1 1 0 0 0-1.898 0l-.683 2.051a1 1 0 0 1-.633.633l-2.051.683a1 1 0 0 0 0 1.898l2.051.684a1 1 0 0 1 .633.632l.683 2.051a1 1 0 0 0 1.898 0l.683-2.051a1 1 0 0 1 .633-.633l2.051-.683a1 1 0 0 0 0-1.898l-2.051-.683a1 1 0 0 1-.633-.633L6.95 5.684ZM13.949 13.684a1 1 0 0 0-1.898 0l-.184.551a1 1 0 0 1-.632.633l-.551.183a1 1 0 0 0 0 1.898l.551.183a1 1 0 0 1 .633.633l.183.551a1 1 0 0 0 1.898 0l.184-.551a1 1 0 0 1 .632-.633l.551-.183a1 1 0 0 0 0-1.898l-.551-.184a1 1 0 0 1-.633-.632l-.183-.551Z" />
- </svg>
- )}
- </button>
- {/* </DialogTrigger> */}
- </TooltipTrigger>
- <TooltipContent className="anycontext-p-0" side="left">
- <p className="anycontext-p-0 anycontext-m-0">
- {savedWebsites.includes(window.location.href)
- ? "Added to memory"
- : "Add to memory"}
- </p>
- </TooltipContent>
- </Tooltip>
- {/* <DialogContent>
+ {savedWebsites.includes(window.location.href) ? (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ strokeWidth="2"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ className="lucide lucide-file-check-2"
+ >
+ <path d="M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" />
+ <path d="M14 2v4a2 2 0 0 0 2 2h4" />
+ <path d="m3 15 2 2 4-4" />
+ </svg>
+ ) : (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 20 20"
+ fill="currentColor"
+ className={`anycontext-w-5 anycontext-h-5 ${isSendingData ? "anycontext-animate-spin" : ""}`}
+ >
+ <path d="M15.98 1.804a1 1 0 0 0-1.96 0l-.24 1.192a1 1 0 0 1-.784.785l-1.192.238a1 1 0 0 0 0 1.962l1.192.238a1 1 0 0 1 .785.785l.238 1.192a1 1 0 0 0 1.962 0l.238-1.192a1 1 0 0 1 .785-.785l1.192-.238a1 1 0 0 0 0-1.962l-1.192-.238a1 1 0 0 1-.785-.785l-.238-1.192ZM6.949 5.684a1 1 0 0 0-1.898 0l-.683 2.051a1 1 0 0 1-.633.633l-2.051.683a1 1 0 0 0 0 1.898l2.051.684a1 1 0 0 1 .633.632l.683 2.051a1 1 0 0 0 1.898 0l.683-2.051a1 1 0 0 1 .633-.633l2.051-.683a1 1 0 0 0 0-1.898l-2.051-.683a1 1 0 0 1-.633-.633L6.95 5.684ZM13.949 13.684a1 1 0 0 0-1.898 0l-.184.551a1 1 0 0 1-.632.633l-.551.183a1 1 0 0 0 0 1.898l.551.183a1 1 0 0 1 .633.633l.183.551a1 1 0 0 0 1.898 0l.184-.551a1 1 0 0 1 .632-.633l.551-.183a1 1 0 0 0 0-1.898l-.551-.184a1 1 0 0 1-.633-.632l-.183-.551Z" />
+ </svg>
+ )}
+ </button>
+ </DialogTrigger>
+ </TooltipTrigger>
+ <TooltipContent className="anycontext-p-0" side="left">
+ <p className="anycontext-p-0 anycontext-m-0">
+ {savedWebsites.includes(window.location.href)
+ ? "Added to memory"
+ : "Add to memory"}
+ </p>
+ </TooltipContent>
+ </Tooltip>
+ <DialogContent>
<DialogHeader>
<DialogTitle>Add to Memory</DialogTitle>
<DialogDescription>
@@ -322,27 +325,33 @@ function SideBar({ jwt }: { jwt: string }) {
</DialogHeader>
<FilterSpaces
+ loading={loading}
className="anycontext-mr-auto"
selectedSpaces={selectedSpaces}
setSelectedSpaces={setSelectedSpaces}
name={"Add to Spaces"}
- spaces={[
- {
- name: "cool tech",
- id: 0,
- },
- {
- name: "cool libs",
- id: 1,
- },
- ]}
+ spaces={spaces ?? []}
/>
<DialogFooter className="anycontext-w-full anycontext-text-sm">
- <DialogClose>Add</DialogClose>
+ <DialogClose
+ onClick={() => {
+ sendUrlToAPI(selectedSpaces);
+ setIsSendingData(true);
+ setTimeout(() => {
+ setIsSendingData(false);
+ setSavedWebsites([
+ ...savedWebsites,
+ window.location.href,
+ ]);
+ }, 1000);
+ }}
+ >
+ Add
+ </DialogClose>
<DialogClose>Cancel</DialogClose>
</DialogFooter>
</DialogContent>
- </Dialog> */}
+ </Dialog>
</div>
</TooltipProvider>
</>
diff --git a/apps/extension/src/background.ts b/apps/extension/src/background.ts
index 7e12bba4..2c67936b 100644
--- a/apps/extension/src/background.ts
+++ b/apps/extension/src/background.ts
@@ -1,4 +1,5 @@
import { getEnv } from "./util";
+import { Space } from "./types/memory";
const backendUrl =
getEnv() === "development"
@@ -48,22 +49,51 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
} else if (request.type === "urlChange") {
const content = request.content;
const url = request.url;
-
- (async () => {
- chrome.storage.local.get(["jwt"], ({ jwt }) => {
+ const spaces = request.spaces(
+ // eslint-disable-next-line no-unexpected-multiline
+ async () => {
+ chrome.storage.local.get(["jwt"], ({ jwt }) => {
+ if (!jwt) {
+ console.error("No JWT found");
+ return;
+ }
+ fetch(`${backendUrl}/api/store`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${jwt}`,
+ },
+ body: JSON.stringify({ pageContent: content, url, spaces }),
+ }).then((ers) => console.log(ers.status));
+ });
+ },
+ )();
+ return true;
+ } else if (request.type === "fetchSpaces") {
+ const run = () =>
+ chrome.storage.local.get(["jwt"], async ({ jwt }) => {
if (!jwt) {
console.error("No JWT found");
return;
}
- fetch(`${backendUrl}/api/store`, {
- method: "POST",
+ const resp = await fetch(`${backendUrl}/api/spaces`, {
headers: {
Authorization: `Bearer ${jwt}`,
},
- body: JSON.stringify({ pageContent: content, url }),
- }).then((ers) => console.log(ers.status));
+ });
+
+ const data: {
+ message: "OK" | string;
+ data: Space[] | undefined;
+ } = await resp.json();
+
+ if (data.message === "OK" && data.data) {
+ sendResponse(data.data);
+ }
});
- })();
+
+ run();
+
+ return true;
} else if (request.type === "queryApi") {
const input = request.input;
const jwt = request.jwt;
diff --git a/apps/extension/src/components/FilterCombobox.tsx b/apps/extension/src/components/FilterCombobox.tsx
index 5467655b..ae9c45ae 100644
--- a/apps/extension/src/components/FilterCombobox.tsx
+++ b/apps/extension/src/components/FilterCombobox.tsx
@@ -1,152 +1,91 @@
import * as React from "react";
-import { Check, ChevronsUpDown, X } from "lucide-react";
-
-import { cn } from "../lib/utils";
-import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
-} from "../components/ui/command";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "../components/ui/popover";
+import { PlusCircleIcon, X } from "lucide-react";
import { Space } from "../types/memory";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+} from "./ui/dropdown-menu";
+import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
- side?: "top" | "bottom";
- align?: "end" | "start" | "center";
- onClose?: () => void;
selectedSpaces: number[];
setSelectedSpaces: (
spaces: number[] | ((prev: number[]) => number[]),
) => void;
name: string;
spaces: Space[];
+ loading: boolean;
}
export function FilterSpaces({
- className,
- side = "bottom",
- align = "center",
- onClose,
+ loading,
selectedSpaces,
setSelectedSpaces,
- name,
spaces,
- ...props
}: Props) {
- const [open, setOpen] = React.useState(false);
-
console.log(selectedSpaces, spaces);
- const sortedSpaces = spaces.sort(({ id: a }, { id: b }) =>
- selectedSpaces.includes(a) && !selectedSpaces.includes(b)
- ? -1
- : selectedSpaces.includes(b) && !selectedSpaces.includes(a)
- ? 1
- : 0,
+ const filteredSpaces = spaces.filter((space) =>
+ selectedSpaces.includes(space.id),
+ );
+ const leftSpaces = spaces.filter(
+ (space) => !selectedSpaces.includes(space.id),
);
- React.useEffect(() => {
- if (!open) {
- onClose?.();
- }
- }, [open]);
+ if (loading) {
+ return "Loading...";
+ }
return (
<div className="anycontext-flex anycontext-flex-wrap anycontext-gap-1 anycontext-text-sm anycontext-">
- {selectedSpaces.map((spaceid) => {
- const space = spaces.find((s) => s.id === spaceid)!;
- return <SpaceItem {...space} key={spaceid} />;
- })}
+ {filteredSpaces.length < 1 && "Add to a space"}
+ {filteredSpaces.map((space) => (
+ <SpaceItem
+ {...space}
+ key={space.id}
+ // onRemove={() => setSelectedSpaces(prev => prev.filter(s => s !== space.id))}
+ />
+ ))}
+ {leftSpaces.length > 0 && (
+ <DropdownMenu>
+ <DropdownMenuTrigger className="anycontext-rounded-full">
+ <PlusCircleIcon
+ className="anycontext-w-5 anycontext-h-5 [--anycontext-icon-stroke:white] dark:[--anycontext-icon-stroke:black]"
+ stroke="var(--anycontext-icon-stroke)"
+ fill="currentColor"
+ />
+ </DropdownMenuTrigger>
+ <DropdownMenuContent>
+ {leftSpaces.map((space) => (
+ <>
+ {loading && "Loading..."}
+ <DropdownMenuItem
+ onClick={() =>
+ setSelectedSpaces((prev) => [...prev, space.id])
+ }
+ >
+ {space.name}
+ </DropdownMenuItem>
+ </>
+ ))}
+ </DropdownMenuContent>
+ </DropdownMenu>
+ )}
</div>
);
-
- return (
- <Popover open={open} onOpenChange={setOpen}>
- <PopoverTrigger asChild>
- <button
- type={undefined}
- data-state-on={open}
- className={cn(
- "anycontext-combobox-button anycontext-w-fit",
- className,
- )}
- {...props}
- >
- {name}
- <ChevronsUpDown className="anycontext-h-4 anycontext-w-4" />
- <div
- data-state-on={selectedSpaces.length > 0}
- className="on:anycontext-flex anycontext-text-rgray-11 anycontext-border-rgray-6 anycontext-bg-rgray-2 anycontext-absolute anycontext-left-0 anycontext-top-0 anycontext-hidden anycontext-aspect-[1] anycontext-h-4 anycontext-w-4 anycontext--translate-x-1/3 anycontext--translate-y-1/3 anycontext-items-center anycontext-justify-center anycontext-rounded-full anycontext-border anycontext-text-center anycontext-text-[9px]"
- >
- {selectedSpaces.length}
- </div>
- </button>
- </PopoverTrigger>
- <PopoverContent
- onCloseAutoFocus={(e) => e.preventDefault()}
- align={align}
- side={side}
- className="anycontext-w-[200px] anycontext-p-0"
- >
- <Command
- filter={(val, search) =>
- spaces
- .find((s) => s.id.toString() === val)
- ?.name.toLowerCase()
- .includes(search.toLowerCase().trim())
- ? 1
- : 0
- }
- >
- <CommandInput placeholder="Filter spaces..." />
- <CommandList asChild>
- <div>
- <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
- >
- <div className="anycontext-text-black/90 dark:anycontext-text-white/90">
- {space.name}
- <Check
- data-state-on={selectedSpaces.includes(space.id)}
- className={cn(
- "on:anycontext-opacity-100 anycontext-ml-auto anycontext-h-4 anycontext-w-4 anycontext-opacity-0",
- )}
- />
- </div>
- </CommandItem>
- ))}
- </CommandGroup>
- </div>
- </CommandList>
- </Command>
- </PopoverContent>
- </Popover>
- );
}
function SpaceItem({ name }: Space) {
return (
<div className="anycontext-flex anycontext-justify-center anycontext-items-center anycontext-gap-2 anycontext-p-1 anycontext-pl-2 anycontext-pr-3 anycontext-rounded-full anycontext-bg-black/5 dark:anycontext-bg-white/5 anycontext-border-white/20 dark:anycontext-border-black/20 border">
- <button className="anycontext-flex hover:anycontext-bg-transparent anycontext-justify-center anycontext-scale-110 anycontext-items-center focus-visible:anycontext-outline-none anycontext-rounded-full anycontext-w-3 anycontext-bg-black/5 dark:anycontext-bg-white/5 anycontext-h-3 anycontext-text-transparent hover:anycontext-text-black dark:hover:anycontext-text-white">
+ <button
+ onClick={
+ // onRemove
+ (e) => e.stopPropagation()
+ }
+ className="anycontext-flex hover:anycontext-bg-transparent anycontext-justify-center anycontext-scale-110 anycontext-items-center focus-visible:anycontext-outline-none anycontext-rounded-full anycontext-w-3 anycontext-bg-black/5 dark:anycontext-bg-white/5 anycontext-h-3 anycontext-text-transparent hover:anycontext-text-black dark:hover:anycontext-text-white"
+ >
<X className="anycontext-w-3 anycontext-h-3" />
</button>
{name}
diff --git a/apps/extension/src/components/ui/dropdown-menu.tsx b/apps/extension/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 00000000..fcc1edb2
--- /dev/null
+++ b/apps/extension/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,204 @@
+import * as React from "react";
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { Check, ChevronRight, Circle } from "lucide-react";
+
+import { cn } from "../../lib/utils";
+
+const DropdownMenu = DropdownMenuPrimitive.Root;
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group;
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub;
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
+ inset?: boolean;
+ }
+>(({ className, inset, children, ...props }, ref) => (
+ <DropdownMenuPrimitive.SubTrigger
+ ref={ref}
+ className={cn(
+ "anycontext-flex anycontext-cursor-default anycontext-select-none anycontext-items-center anycontext-rounded-sm anycontext-px-2 anycontext-py-1.5 anycontext-text-sm anycontext-outline-none focus:anycontext-bg-stone-100 data-[state=open]:anycontext-bg-stone-100 dark:focus:anycontext-bg-stone-800 dark:data-[state=open]:anycontext-bg-stone-800",
+ inset && "anycontext-pl-8",
+ className,
+ )}
+ {...props}
+ >
+ {children}
+ <ChevronRight className="anycontext-ml-auto anycontext-h-4 anycontext-w-4" />
+ </DropdownMenuPrimitive.SubTrigger>
+));
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName;
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+ <DropdownMenuPrimitive.SubContent
+ ref={ref}
+ className={cn(
+ "anycontext-z-50 anycontext-min-w-[8rem] anycontext-overflow-hidden anycontext-rounded-md anycontext-border anycontext-border-stone-200 anycontext-bg-white anycontext-p-1 anycontext-text-stone-950 anycontext-shadow-lg data-[state=open]:anycontext-animate-in data-[state=closed]:anycontext-animate-out data-[state=closed]:anycontext-fade-out-0 data-[state=open]:anycontext-fade-in-0 data-[state=closed]:anycontext-zoom-out-95 data-[state=open]:anycontext-zoom-in-95 data-[side=bottom]:anycontext-slide-in-from-top-2 data-[side=left]:anycontext-slide-in-from-right-2 data-[side=right]:anycontext-slide-in-from-left-2 data-[side=top]:anycontext-slide-in-from-bottom-2 dark:anycontext-border-stone-800 dark:anycontext-bg-stone-950 dark:anycontext-text-stone-50",
+ className,
+ )}
+ {...props}
+ />
+));
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName;
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
+>(({ className, sideOffset = 4, ...props }, ref) => (
+ <DropdownMenuPrimitive.Portal>
+ <DropdownMenuPrimitive.Content
+ ref={ref}
+ sideOffset={sideOffset}
+ className={cn(
+ "anycontext-z-50 anycontext-min-w-[8rem] anycontext-overflow-hidden anycontext-rounded-md anycontext-border anycontext-border-stone-200 anycontext-bg-white anycontext-p-1 anycontext-text-stone-950 anycontext-shadow-md data-[state=open]:anycontext-animate-in data-[state=closed]:anycontext-animate-out data-[state=closed]:anycontext-fade-out-0 data-[state=open]:anycontext-fade-in-0 data-[state=closed]:anycontext-zoom-out-95 data-[state=open]:anycontext-zoom-in-95 data-[side=bottom]:anycontext-slide-in-from-top-2 data-[side=left]:anycontext-slide-in-from-right-2 data-[side=right]:anycontext-slide-in-from-left-2 data-[side=top]:anycontext-slide-in-from-bottom-2 dark:anycontext-border-stone-800 dark:anycontext-bg-stone-950 dark:anycontext-text-stone-50",
+ className,
+ )}
+ {...props}
+ />
+ </DropdownMenuPrimitive.Portal>
+));
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
+ inset?: boolean;
+ }
+>(({ className, inset, ...props }, ref) => (
+ <DropdownMenuPrimitive.Item
+ ref={ref}
+ className={cn(
+ "anycontext-relative anycontext-flex anycontext-cursor-default anycontext-select-none anycontext-items-center anycontext-rounded-sm anycontext-px-2 anycontext-py-1.5 anycontext-text-sm anycontext-outline-none anycontext-transition-colors focus:anycontext-bg-stone-100 focus:anycontext-text-stone-900 data-[disabled]:anycontext-pointer-events-none data-[disabled]:anycontext-opacity-50 dark:focus:anycontext-bg-stone-800 dark:focus:anycontext-text-stone-50",
+ inset && "anycontext-pl-8",
+ className,
+ )}
+ {...props}
+ />
+));
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+ <DropdownMenuPrimitive.CheckboxItem
+ ref={ref}
+ className={cn(
+ "anycontext-relative anycontext-flex anycontext-cursor-default anycontext-select-none anycontext-items-center anycontext-rounded-sm anycontext-py-1.5 anycontext-pl-8 anycontext-pr-2 anycontext-text-sm anycontext-outline-none anycontext-transition-colors focus:anycontext-bg-stone-100 focus:anycontext-text-stone-900 data-[disabled]:anycontext-pointer-events-none data-[disabled]:anycontext-opacity-50 dark:focus:anycontext-bg-stone-800 dark:focus:anycontext-text-stone-50",
+ className,
+ )}
+ checked={checked}
+ {...props}
+ >
+ <span className="anycontext-absolute anycontext-left-2 anycontext-flex anycontext-h-3.5 anycontext-w-3.5 anycontext-items-center anycontext-justify-center">
+ <DropdownMenuPrimitive.ItemIndicator>
+ <Check className="anycontext-h-4 anycontext-w-4" />
+ </DropdownMenuPrimitive.ItemIndicator>
+ </span>
+ {children}
+ </DropdownMenuPrimitive.CheckboxItem>
+));
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName;
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+ <DropdownMenuPrimitive.RadioItem
+ ref={ref}
+ className={cn(
+ "anycontext-relative anycontext-flex anycontext-cursor-default anycontext-select-none anycontext-items-center anycontext-rounded-sm anycontext-py-1.5 anycontext-pl-8 anycontext-pr-2 anycontext-text-sm anycontext-outline-none anycontext-transition-colors focus:anycontext-bg-stone-100 focus:anycontext-text-stone-900 data-[disabled]:anycontext-pointer-events-none data-[disabled]:anycontext-opacity-50 dark:focus:anycontext-bg-stone-800 dark:focus:anycontext-text-stone-50",
+ className,
+ )}
+ {...props}
+ >
+ <span className="anycontext-absolute anycontext-left-2 anycontext-flex anycontext-h-3.5 anycontext-w-3.5 anycontext-items-center anycontext-justify-center">
+ <DropdownMenuPrimitive.ItemIndicator>
+ <Circle className="anycontext-h-2 anycontext-w-2 anycontext-fill-current" />
+ </DropdownMenuPrimitive.ItemIndicator>
+ </span>
+ {children}
+ </DropdownMenuPrimitive.RadioItem>
+));
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
+ inset?: boolean;
+ }
+>(({ className, inset, ...props }, ref) => (
+ <DropdownMenuPrimitive.Label
+ ref={ref}
+ className={cn(
+ "anycontext-px-2 anycontext-py-1.5 anycontext-text-sm anycontext-font-semibold",
+ inset && "anycontext-pl-8",
+ className,
+ )}
+ {...props}
+ />
+));
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+ <DropdownMenuPrimitive.Separator
+ ref={ref}
+ className={cn(
+ "anycontext--mx-1 anycontext-my-1 anycontext-h-px anycontext-bg-stone-100 dark:anycontext-bg-stone-800",
+ className,
+ )}
+ {...props}
+ />
+));
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+ return (
+ <span
+ className={cn(
+ "anycontext-ml-auto anycontext-text-xs anycontext-tracking-widest anycontext-opacity-60",
+ className,
+ )}
+ {...props}
+ />
+ );
+};
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+};
diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts
index 46e3ddf6..35ebe423 100644
--- a/apps/web/src/actions/db.ts
+++ b/apps/web/src/actions/db.ts
@@ -7,35 +7,40 @@ import {
StoredContent,
storedContent,
users,
- space
+ space,
} from "@/server/db/schema";
import { like, eq, and, sql } from "drizzle-orm";
-import { union } from "drizzle-orm/sqlite-core"
+import { union } from "drizzle-orm/sqlite-core";
import { auth as authOptions } from "@/server/auth";
+import { FormEvent } from "react";
+import { revalidatePath } from "next/cache";
// @todo: (future) pagination not yet needed
export async function searchMemoriesAndSpaces(userId: string, query: string) {
- const searchMemoriesQuery = db.select({
- type: sql<string>`'memory'`,
- space: sql`NULL`,
- memory: storedContent as any
- }).from(storedContent).where(and(
- eq(storedContent.user, userId),
- like(storedContent.title, `%${query}%`)
- ))
-
- const searchSpacesQuery = db.select({
- type: sql<string>`'space'`,
- space: space as any,
- memory: sql`NULL`,
- }).from(space).where(
- and(
- eq(space.user, userId),
- like(space.name, `%${query}%`)
- )
- )
-
- return await union(searchMemoriesQuery, searchSpacesQuery)
+ const searchMemoriesQuery = db
+ .select({
+ type: sql<string>`'memory'`,
+ space: sql`NULL`,
+ memory: storedContent as any,
+ })
+ .from(storedContent)
+ .where(
+ and(
+ eq(storedContent.user, userId),
+ like(storedContent.title, `%${query}%`),
+ ),
+ );
+
+ const searchSpacesQuery = db
+ .select({
+ type: sql<string>`'space'`,
+ space: space as any,
+ memory: sql`NULL`,
+ })
+ .from(space)
+ .where(and(eq(space.user, userId), like(space.name, `%${query}%`)));
+
+ return await union(searchMemoriesQuery, searchSpacesQuery);
}
async function getUser() {
@@ -46,7 +51,7 @@ async function getUser() {
headers().get("Authorization")?.replace("Bearer ", "");
if (!token) {
- return null
+ return null;
}
const session = await db
@@ -55,7 +60,7 @@ async function getUser() {
.where(eq(sessions.sessionToken, token!));
if (!session || session.length === 0) {
- return null
+ return null;
}
const [userData] = await db
@@ -65,17 +70,17 @@ async function getUser() {
.limit(1);
if (!userData) {
- return null
+ return null;
}
- return userData
+ return userData;
}
export async function getMemory(title: string) {
const user = await getUser();
if (!user) {
- return null
+ return null;
}
return await db
@@ -93,11 +98,10 @@ export async function addMemory(
content: typeof storedContent.$inferInsert,
spaces: number[],
) {
-
const user = await getUser();
if (!user) {
- return null
+ return null;
}
content.user = user.id;
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 419daa5a..3112e71e 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -1,5 +1,6 @@
import { db } from "@/server/db";
import {
+ ChachedSpaceContent,
contentToSpace,
sessions,
space,
@@ -18,6 +19,7 @@ import {
import { MemoryProvider } from "@/contexts/MemoryContext";
import Content from "./content";
import { searchMemoriesAndSpaces } from "@/actions/db";
+import { getMetaData } from "@/server/helpers";
export const runtime = "edge";
@@ -56,36 +58,37 @@ export default async function Home() {
const collectedSpaces = await db
.select()
.from(space)
- .where(and(eq(space.user, userData.id), not(eq(space.name, "none"))));
+ .where(eq(space.user, userData.id))
+ .all();
+
+ console.log(collectedSpaces);
// Fetch only first 3 content of each spaces
- let contents: (typeof storedContent.$inferSelect)[] = [];
+ let contents: ChachedSpaceContent[] = [];
+
+ //console.log(await db.select().from(storedContent).)
await Promise.all([
collectedSpaces.forEach(async (space) => {
- contents = [
- ...contents,
- ...(await fetchContentForSpace(space.id, {
+ console.log("fetching ");
+ const data = (
+ await fetchContentForSpace(space.id, {
offset: 0,
limit: 3,
- })),
- ];
+ })
+ ).map((data) => ({
+ ...data,
+ space: space.id,
+ }));
+ contents = [...contents, ...data];
}),
]);
+ console.log(contents);
+
// freeMemories
const freeMemories = await fetchFreeMemories(userData.id);
-
- // @dhravya test these 3 functions
- fetchFreeMemories;
- fetchContentForSpace;
- searchMemoriesAndSpaces;
-
- collectedSpaces.push({
- id: 1,
- name: "Cool tech",
- user: null,
- });
+ console.log("free", freeMemories);
return (
<MemoryProvider
diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
index 886507ff..08b9a750 100644
--- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
+++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
@@ -153,37 +153,6 @@ 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);
-
- 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.dataset["error"] = "true";
- setTimeout(() => {
- inputRef.current!.placeholder = "Title of the note";
- inputRef.current!.dataset["error"] = "false";
- }, 500);
- inputRef.current.focus();
- return false;
- }
- return true;
- }
-
return (
<div className="md:w-[40vw]">
<DialogHeader>
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
index 769e6296..f671b72f 100644
--- a/apps/web/src/components/Sidebar/MemoriesBar.tsx
+++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx
@@ -23,7 +23,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
import { Variant, useAnimate, motion } from "framer-motion";
import { useMemory } from "@/contexts/MemoryContext";
import { SpaceIcon } from "@/assets/Memories";
@@ -42,11 +42,12 @@ import useTouchHold from "@/hooks/useTouchHold";
import { DialogTrigger } from "@radix-ui/react-dialog";
import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog";
import { ExpandedSpace } from "./ExpandedSpace";
-import { StoredSpace } from "@/server/db/schema";
+import { StoredContent, StoredSpace } from "@/server/db/schema";
+import Image from "next/image";
export function MemoriesBar() {
const [parent, enableAnimations] = useAutoAnimate();
- const { spaces, deleteSpace } = useMemory();
+ const { spaces, deleteSpace, freeMemories } = useMemory();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [addMemoryState, setAddMemoryState] = useState<
@@ -124,12 +125,15 @@ export function MemoriesBar() {
>
{spaces.map((space) => (
<SpaceItem
- onDelete={() => deleteSpace(space.id)}
+ onDelete={() => {}}
key={space.id}
- onClick={() => setExpandedSpace(space.id)}
+ //onClick={() => setExpandedSpace(space.id)}
{...space}
/>
))}
+ {freeMemories.map((m) => (
+ <MemoryItem {...m} key={m.id} />
+ ))}
</div>
</div>
);
@@ -145,12 +149,28 @@ const SpaceExitVariant: Variant = {
},
};
+export function MemoryItem({ id, title, image }: StoredContent) {
+ 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">
+ {title}
+ </button>
+
+ <div className="flex h-24 w-24 items-center justify-center">
+ <img className="h-16 w-16" id={id.toString()} src={image!} />
+ </div>
+ </div>
+ );
+}
+
export function SpaceItem({
name,
id,
onDelete,
onClick,
}: StoredSpace & { onDelete: () => void; onClick?: () => void }) {
+ const { cachedMemories } = useMemory();
+
const [itemRef, animateItem] = useAnimate();
const { width } = useViewport();
@@ -162,6 +182,10 @@ export function SpaceItem({
},
});
+ const spaceMemories = useMemo(() => {
+ return cachedMemories.filter((m) => m.space === id);
+ }, [cachedMemories]);
+
return (
<motion.div
ref={itemRef}
@@ -176,104 +200,106 @@ export function SpaceItem({
isOpen={moreDropdownOpen}
setIsOpen={setMoreDropdownOpen}
onDelete={() => {
+ 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();
+ // };
+ // };
}}
/>
- {/* {content.length > 2 ? (
+ {spaceMemories.length > 2 ? (
<MemoryWithImages3
className="h-24 w-24"
id={id.toString()}
- images={content.map((c) => c.image).reverse() as string[]}
+ images={spaceMemories.map((c) => c.image).reverse() as string[]}
/>
- ) : content.length === 1 ? (
+ ) : spaceMemories.length === 1 ? (
<MemoryWithImage
className="h-24 w-24"
id={id.toString()}
- image={content[0].image!}
+ image={spaceMemories[0].image!}
/>
) : (
<MemoryWithImages2
className="h-24 w-24"
id={id.toString()}
- images={content.map((c) => c.image).reverse() as string[]}
+ images={spaceMemories.map((c) => c.image).reverse() as string[]}
/>
- )} */}
+ )}
</motion.div>
);
}
@@ -288,7 +314,7 @@ export function SpaceMoreButton({
setIsOpen?: (open: boolean) => void;
}) {
return (
- <>
+ <Dialog>
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger asChild>
<button
@@ -310,16 +336,36 @@ export function SpaceMoreButton({
<Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
Edit
</DropdownMenuItem>
- <DropdownMenuItem
- onClick={onDelete}
- 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} />
- Move to Trash
- </DropdownMenuItem>
+ <DialogTrigger asChild>
+ <DropdownMenuItem
+ onClick={onDelete}
+ 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} />
+ Move to Trash
+ </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="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/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx
index aa9cab81..0438d75e 100644
--- a/apps/web/src/contexts/MemoryContext.tsx
+++ b/apps/web/src/contexts/MemoryContext.tsx
@@ -1,15 +1,20 @@
"use client";
import React, { useCallback } from "react";
import { CollectedSpaces } from "../../types/memory";
-import { StoredContent, storedContent, StoredSpace } from "@/server/db/schema";
+import {
+ ChachedSpaceContent,
+ StoredContent,
+ storedContent,
+ StoredSpace,
+} from "@/server/db/schema";
import { addMemory, searchMemoriesAndSpaces } from "@/actions/db";
import { User } from "next-auth";
export type SearchResult = {
- type: "memory" | "space",
- space: StoredSpace,
- memory: StoredContent
-}
+ type: "memory" | "space";
+ space: StoredSpace;
+ memory: StoredContent;
+};
// temperory (will change)
export const MemoryContext = React.createContext<{
@@ -21,8 +26,8 @@ export const MemoryContext = React.createContext<{
memory: typeof storedContent.$inferInsert,
spaces?: number[],
) => Promise<void>;
- cachedMemories: StoredContent[];
- search: (query: string) => Promise<SearchResult[]>;
+ cachedMemories: ChachedSpaceContent[];
+ search: (query: string) => Promise<SearchResult[]>;
}>({
spaces: [],
freeMemories: [],
@@ -30,57 +35,62 @@ export const MemoryContext = React.createContext<{
addSpace: async () => {},
deleteSpace: async () => {},
cachedMemories: [],
- search: async () => []
+ search: async () => [],
});
export const MemoryProvider: React.FC<
{
spaces: StoredSpace[];
freeMemories: StoredContent[];
- cachedMemories: StoredContent[];
- user: User;
+ cachedMemories: ChachedSpaceContent[];
+ user: User;
} & React.PropsWithChildren
-> = ({ children, user, spaces: initalSpaces, freeMemories: initialFreeMemories, cachedMemories: initialCachedMemories }) => {
-
+> = ({
+ children,
+ user,
+ spaces: initalSpaces,
+ freeMemories: initialFreeMemories,
+ cachedMemories: initialCachedMemories,
+}) => {
const [spaces, setSpaces] = React.useState<StoredSpace[]>(initalSpaces);
const [freeMemories, setFreeMemories] =
React.useState<StoredContent[]>(initialFreeMemories);
- const [cachedMemories, setCachedMemories] = React.useState<StoredContent[]>(
- initialCachedMemories
- );
+ const [cachedMemories, setCachedMemories] = React.useState<
+ ChachedSpaceContent[]
+ >(initialCachedMemories);
const addSpace = async (space: StoredSpace) => {
- setSpaces((prev) => [...prev, space]);
- }
-
- const deleteSpace = async (id: number) => {
- setSpaces((prev) => prev.filter((s) => s.id !== id));
- }
+ setSpaces((prev) => [...prev, space]);
+ };
- const search = async (query: string) => {
- if (!user.id) {
- throw new Error('user id is not define')
- }
- const data = await searchMemoriesAndSpaces(user.id, query)
- return data as SearchResult[]
- }
+ 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(user.id, query);
+ return data as SearchResult[];
+ };
// const fetchMemories = useCallback(async (query: string) => {
// const response = await fetch(`/api/memories?${query}`);
// }, []);
- const _addMemory = async (
- memory: typeof storedContent.$inferInsert,
- spaces: number[] = [],
- ) => {
- const content = await addMemory(memory, spaces);
- }
+ const _addMemory = async (
+ memory: typeof storedContent.$inferInsert,
+ spaces: number[] = [],
+ ) => {
+ const content = await addMemory(memory, spaces);
+ };
return (
<MemoryContext.Provider
value={{
- search,
+ search,
spaces,
addSpace,
deleteSpace,
diff --git a/apps/web/src/server/db/schema.ts b/apps/web/src/server/db/schema.ts
index daac595c..4ca4332f 100644
--- a/apps/web/src/server/db/schema.ts
+++ b/apps/web/src/server/db/schema.ts
@@ -21,7 +21,7 @@ export const users = createTable("user", {
image: text("image", { length: 255 }),
});
-export type User = typeof users.$inferSelect
+export type User = typeof users.$inferSelect;
export const usersRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
@@ -134,3 +134,6 @@ export const space = createTable(
export type StoredContent = Omit<typeof storedContent.$inferSelect, "user">;
export type StoredSpace = typeof space.$inferSelect;
+export type ChachedSpaceContent = StoredContent & {
+ space: number;
+};
diff --git a/apps/web/src/server/db/test.ts b/apps/web/src/server/db/test.ts
new file mode 100644
index 00000000..37969e5e
--- /dev/null
+++ b/apps/web/src/server/db/test.ts
@@ -0,0 +1,6 @@
+import { db } from ".";
+import { space, user } from "./schema";
+
+const user = await db.select(user).all();
+
+await db.insert(space).values([{}]);
diff --git a/apps/web/types/memory.tsx b/apps/web/types/memory.tsx
index ff0dc94c..6bfda971 100644
--- a/apps/web/types/memory.tsx
+++ b/apps/web/types/memory.tsx
@@ -5,7 +5,7 @@ import {
storedContent,
StoredContent,
} from "@/server/db/schema";
-import { asc, and, eq, inArray, notExists } from "drizzle-orm";
+import { asc, and, eq, inArray, notExists, sql, exists } from "drizzle-orm";
export async function fetchContentForSpace(
spaceId: number,
@@ -14,41 +14,55 @@ export async function fetchContentForSpace(
limit: number;
},
) {
-
const query = db
.select()
.from(storedContent)
.where(
- inArray(
- storedContent.id,
- db.select().from(space).where(eq(space.id, spaceId)),
+ exists(
+ db
+ .select()
+ .from(contentToSpace)
+ .where(
+ and(
+ eq(contentToSpace.spaceId, spaceId),
+ eq(contentToSpace.contentId, storedContent.id),
+ ),
+ ),
),
- ).orderBy(asc(storedContent.title))
+ )
+ .orderBy(asc(storedContent.title));
- return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
+ return range
+ ? await query.limit(range.limit).offset(range.offset)
+ : await query.all();
}
export async function fetchFreeMemories(
- userId: string,
- range?: {
- offset: number;
- limit: number;
- }
+ userId: string,
+ range?: {
+ offset: number;
+ limit: number;
+ },
) {
- const query = db
+ const query = db
.select()
.from(storedContent)
.where(
- and(
- notExists(
- db.select().from(contentToSpace).where(eq(contentToSpace.contentId, storedContent.id)),
- ),
- eq(storedContent.user, userId),
- )
-
- ).orderBy(asc(storedContent.title))
+ and(
+ notExists(
+ db
+ .select()
+ .from(contentToSpace)
+ .where(eq(contentToSpace.contentId, storedContent.id)),
+ ),
+ eq(storedContent.user, userId),
+ ),
+ )
+ .orderBy(asc(storedContent.title));
- return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
+ return range
+ ? await query.limit(range.limit).offset(range.offset)
+ : await query.all();
}
export const transformContent = async (