mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-05-13 15:31:49 +00:00
merge
This commit is contained in:
parent
f0b2412a8b
commit
75cae41f2a
13 changed files with 675 additions and 437 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
<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="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>
|
||||
{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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
204
apps/extension/src/components/ui/dropdown-menu.tsx
Normal file
204
apps/extension/src/components/ui/dropdown-menu.tsx
Normal file
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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 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}%`)
|
||||
)
|
||||
)
|
||||
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)
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
6
apps/web/src/server/db/test.ts
Normal file
6
apps/web/src/server/db/test.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { db } from ".";
|
||||
import { space, user } from "./schema";
|
||||
|
||||
const user = await db.select(user).all();
|
||||
|
||||
await db.insert(space).values([{}]);
|
||||
|
|
@ -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 (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue