aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app/(canvas)
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-06-30 20:50:24 -0500
committerDhravya <[email protected]>2024-06-30 20:50:24 -0500
commitffd141ade4e6074ee486da7f74f31e3905807cb9 (patch)
tree505d73b0a7c04cdec93d7f5be88c635642716c15 /apps/web/app/(canvas)
parentshow updates in the extension (diff)
parentMerge pull request #93 from Dhravya/editor (diff)
downloadsupermemory-ffd141ade4e6074ee486da7f74f31e3905807cb9.tar.xz
supermemory-ffd141ade4e6074ee486da7f74f31e3905807cb9.zip
merge conflicts
Diffstat (limited to 'apps/web/app/(canvas)')
-rw-r--r--apps/web/app/(canvas)/canvas/[id]/page.tsx140
-rw-r--r--apps/web/app/(canvas)/canvas/layout.tsx13
-rw-r--r--apps/web/app/(canvas)/canvas/page.tsx21
-rw-r--r--apps/web/app/(canvas)/canvas/search&create.tsx45
-rw-r--r--apps/web/app/(canvas)/canvas/thinkPad.tsx276
-rw-r--r--apps/web/app/(canvas)/canvas/thinkPads.tsx32
-rw-r--r--apps/web/app/(canvas)/canvasStyles.css32
-rw-r--r--apps/web/app/(canvas)/layout.tsx30
8 files changed, 434 insertions, 155 deletions
diff --git a/apps/web/app/(canvas)/canvas/[id]/page.tsx b/apps/web/app/(canvas)/canvas/[id]/page.tsx
index 6efb6cf4..cad6143e 100644
--- a/apps/web/app/(canvas)/canvas/[id]/page.tsx
+++ b/apps/web/app/(canvas)/canvas/[id]/page.tsx
@@ -1,129 +1,17 @@
-"use client";
-
-import { Canvas } from "@repo/ui/components/canvas/components/canvas";
-import React, { useState } from "react";
-import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
-import { SettingsIcon, DragIcon } from "@repo/ui/icons";
-import DraggableComponentsContainer from "@repo/ui/components/canvas/components/draggableComponent";
-import { AutocompleteIcon, blockIcon } from "@repo/ui/icons";
-import Image from "next/image";
-import { Switch } from "@repo/ui/shadcn/switch";
-import { Label } from "@repo/ui/shadcn/label";
-import { useRouter } from "next/router";
-
-function page() {
- const [fullScreen, setFullScreen] = useState(false);
- const [visible, setVisible] = useState(true);
-
- const router = useRouter();
- router.push("/home");
-
+import { userHasCanvas } from "@/app/actions/fetchers";
+import {
+ RectProvider,
+ ResizaleLayout,
+} from "@/components/canvas/resizableLayout";
+import { redirect } from "next/navigation";
+export default async function page({ params }: any) {
+ const canvasExists = await userHasCanvas(params.id);
+ if (!canvasExists.success) {
+ redirect("/canvas");
+ }
return (
- <div
- className={`h-screen w-full ${!fullScreen ? "px-4 py-6" : "bg-[#1F2428]"} transition-all`}
- >
- <div>
- <PanelGroup
- onLayout={(l) => {
- l[0]! < 20 ? setVisible(false) : setVisible(true);
- }}
- className={` ${fullScreen ? "w-[calc(100vw-2rem)]" : "w-screen"} transition-all`}
- direction="horizontal"
- >
- <Panel
- onExpand={() => {
- setTimeout(() => setFullScreen(false), 50);
- }}
- onCollapse={() => {
- setTimeout(() => setFullScreen(true), 50);
- }}
- defaultSize={30}
- collapsible={true}
- >
- <div
- className={`flex transition-all rounded-2xl ${fullScreen ? "h-screen" : "h-[calc(100vh-3rem)]"} w-full flex-col overflow-hidden bg-[#1F2428]`}
- >
- <div className="flex items-center justify-between bg-[#2C3439] px-4 py-2 text-lg font-medium text-[#989EA4]">
- Change Filters
- <Image src={SettingsIcon} alt="setting-icon" />
- </div>
- {visible ? (
- <SidePanel />
- ) : (
- <h1 className="text-center py-10 text-xl">
- Need more space to show!
- </h1>
- )}
- </div>
- </Panel>
- <PanelResizeHandle
- className={`relative flex items-center transition-all justify-center ${!fullScreen && "px-1"}`}
- >
- <div
- className={`rounded-lg bg-[#2F363B] ${!fullScreen && "px-1"} transition-all py-2`}
- >
- <Image src={DragIcon} alt="drag-icon" />
- </div>
- </PanelResizeHandle>
- <Panel className="relative" defaultSize={70} minSize={60}>
- <div
- className={`absolute overflow-hidden transition-all inset-0 ${fullScreen ? "h-screen " : "h-[calc(100vh-3rem)] rounded-2xl"} w-full`}
- >
- <Canvas />
- </div>
- </Panel>
- </PanelGroup>
- </div>
- </div>
+ <RectProvider id={params.id}>
+ <ResizaleLayout />
+ </RectProvider>
);
}
-
-function SidePanel() {
- const [value, setValue] = useState("");
- const [dragAsText, setDragAsText] = useState(false);
- return (
- <>
- <div className="px-3 py-5">
- <input
- placeholder="search..."
- onChange={(e) => {
- setValue(e.target.value);
- }}
- value={value}
- // rows={1}
- className="w-full resize-none rounded-xl bg-[#151515] px-3 py-4 text-xl text-[#989EA4] outline-none focus:outline-none sm:max-h-52"
- />
- </div>
- <div className="flex items-center justify-end px-3 py-4">
- <Switch
- className="bg-[#151515] data-[state=unchecked]:bg-red-400 data-[state=checked]:bg-blue-400"
- onCheckedChange={(e) => setDragAsText(e)}
- id="drag-text-mode"
- />
- <Label htmlFor="drag-text-mode">Drag as Text</Label>
- </div>
- <DraggableComponentsContainer content={content} />
- </>
- );
-}
-
-export default page;
-
-const content = [
- {
- content:
- "Regional growth patterns diverge, with strong performance in the United States and several emerging markets, contrasted by weaker prospects in many advanced economies, particularly in Europe (World Economic Forum) (OECD). The rapid adoption of artificial intelligence (AI) is expected to drive productivity growth, especially in advanced economies, potentially mitigating labor shortages and boosting income levels in emerging markets (World Economic Forum) (OECD). However, ongoing geopolitical tensions and economic fragmentation are likely to maintain a level of uncertainty and volatility in the global economy (World Economic Forum.",
- icon: AutocompleteIcon,
- iconAlt: "Autocomplete",
- extraInfo:
- "Page Url: https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
- },
- {
- content:
- "As of mid-2024, the global economy is experiencing modest growth with significant regional disparities. Global GDP growth is projected to be around 3.1% in 2024, rising slightly to 3.2% in 2025. This performance, although below the pre-pandemic average, reflects resilience despite various economic pressures, including tight monetary conditions and geopolitical tensions (IMF)(OECD) Inflation is moderating faster than expected, with global headline inflation projected to fall to 5.8% in 2024 and 4.4% in 2025, contributing to improving real incomes and positive trade growth (IMF) (OECD)",
- icon: blockIcon,
- iconAlt: "Autocomplete",
- extraInfo:
- "Page Url: https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia",
- },
-];
diff --git a/apps/web/app/(canvas)/canvas/layout.tsx b/apps/web/app/(canvas)/canvas/layout.tsx
deleted file mode 100644
index 9bc3b6d7..00000000
--- a/apps/web/app/(canvas)/canvas/layout.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import "../canvasStyles.css";
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- return (
- <div lang="en" className="bg-[#151515]">
- <div>{children}</div>
- </div>
- );
-}
diff --git a/apps/web/app/(canvas)/canvas/page.tsx b/apps/web/app/(canvas)/canvas/page.tsx
index 8b5252af..d0824a20 100644
--- a/apps/web/app/(canvas)/canvas/page.tsx
+++ b/apps/web/app/(canvas)/canvas/page.tsx
@@ -1,9 +1,22 @@
-import { redirect } from "next/navigation";
import React from "react";
+import { getCanvas } from "@/app/actions/fetchers";
+import SearchandCreate from "./search&create";
+import ThinkPads from "./thinkPads";
-function page() {
- redirect("/signin");
- return <div>page</div>;
+async function page() {
+ const canvas = await getCanvas();
+ return (
+ <div className="h-screen w-full py-32 text-[#FFFFFF] ">
+ <div className="flex w-full flex-col items-center gap-8">
+ <h1 className="text-4xl font-medium">Your thinkpads</h1>
+ <SearchandCreate />
+ {
+ // @ts-ignore
+ canvas.success && <ThinkPads data={canvas.data} />
+ }
+ </div>
+ </div>
+ );
}
export default page;
diff --git a/apps/web/app/(canvas)/canvas/search&create.tsx b/apps/web/app/(canvas)/canvas/search&create.tsx
new file mode 100644
index 00000000..e73ad76f
--- /dev/null
+++ b/apps/web/app/(canvas)/canvas/search&create.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import { useFormStatus } from "react-dom";
+import Image from "next/image";
+import { SearchIcon } from "@repo/ui/icons";
+import { createCanvas } from "@/app/actions/doers";
+import { toast } from "sonner";
+
+export default function SearchandCreate() {
+ return (
+ <div className="flex w-[90%] max-w-2xl gap-2">
+ <div className="flex flex-grow items-center overflow-hidden rounded-xl bg-[#1F2428]">
+ <input
+ placeholder="search here..."
+ className="flex-grow bg-[#1F2428] px-5 py-3 text-xl focus:border-none focus:outline-none"
+ />
+ <button className="h-full border-l-2 border-[#384149] px-2 pl-2">
+ <Image src={SearchIcon} alt="search" />
+ </button>
+ </div>
+
+ <form
+ action={async () => {
+ const res = await createCanvas();
+ if (!res.success) {
+ toast.warning(res.message, {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ }
+ }}
+ >
+ <Button />
+ </form>
+ </div>
+ );
+}
+
+function Button() {
+ const { pending } = useFormStatus();
+ return (
+ <button className="rounded-xl bg-[#1F2428] px-5 py-3 text-xl text-[#B8C4C6]">
+ {pending ? "Creating.." : "Create New"}
+ </button>
+ );
+}
diff --git a/apps/web/app/(canvas)/canvas/thinkPad.tsx b/apps/web/app/(canvas)/canvas/thinkPad.tsx
new file mode 100644
index 00000000..fff30f26
--- /dev/null
+++ b/apps/web/app/(canvas)/canvas/thinkPad.tsx
@@ -0,0 +1,276 @@
+import { getCanvasData } from "@/app/actions/fetchers";
+import { AnimatePresence, motion } from "framer-motion";
+import Link from "next/link";
+import {
+ EllipsisHorizontalCircleIcon,
+ TrashIcon,
+ PencilSquareIcon,
+} from "@heroicons/react/24/outline";
+import { toast } from "sonner";
+import { Label } from "@repo/ui/shadcn/label";
+
+const childVariants = {
+ hidden: { opacity: 0, y: 10, filter: "blur(2px)" },
+ visible: { opacity: 1, y: 0, filter: "blur(0px)" },
+};
+
+export default function ThinkPad({
+ title,
+ description,
+ image,
+ id,
+}: {
+ title: string;
+ description: string;
+ image: string;
+ id: string;
+}) {
+ const [deleted, setDeleted] = useState(false);
+ const [info, setInfo] = useState({ title, description });
+ return (
+ <AnimatePresence mode="sync">
+ {!deleted && (
+ <motion.div
+ layout
+ exit={{ opacity: 0, scaleY: 0 }}
+ variants={childVariants}
+ className="flex h-48 origin-top relative gap-4 rounded-2xl bg-[#1F2428] p-2"
+ >
+ <Link
+ className="h-full select-none min-w-[40%] bg-[#363f46] rounded-xl overflow-hidden"
+ href={`/canvas/${id}`}
+ >
+ <Suspense
+ fallback={
+ <div className=" h-full w-full flex justify-center items-center">
+ Loading...
+ </div>
+ }
+ >
+ <ImageComponent id={id} />
+ </Suspense>
+ </Link>
+ <div className="flex flex-col gap-2">
+ <motion.h2
+ initial={{ opacity: 0, filter: "blur(3px)" }}
+ animate={{ opacity: 1, filter: "blur(0px)" }}
+ key={info.title}
+ >
+ {info.title}
+ </motion.h2>
+ <motion.h3
+ key={info.description}
+ initial={{ opacity: 0, filter: "blur(3px)" }}
+ animate={{ opacity: 1, filter: "blur(0px)" }}
+ className="overflow-hidden text-ellipsis text-[#B8C4C6]"
+ >
+ {info.description}
+ </motion.h3>
+ </div>
+ <Menu
+ info={info}
+ id={id}
+ setDeleted={() => setDeleted(true)}
+ setInfo={(e) => setInfo(e)}
+ />
+ </motion.div>
+ )}
+ </AnimatePresence>
+ );
+}
+
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@repo/ui/shadcn/popover";
+
+function Menu({
+ info,
+ id,
+ setDeleted,
+ setInfo,
+}: {
+ info: { title: string; description: string };
+ id: string;
+ setDeleted: () => void;
+ setInfo: ({
+ title,
+ description,
+ }: {
+ title: string;
+ description: string;
+ }) => void;
+}) {
+ return (
+ <Popover>
+ <PopoverTrigger className="absolute z-20 top-0 right-0" asChild>
+ <Button variant="secondary">
+ <EllipsisHorizontalCircleIcon className="size-5 stroke-2 stroke-[#B8C4C6]" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent
+ align="start"
+ className="w-32 px-2 py-2 bg-[#161f2a]/30 text-[#B8C4C6] border-border flex flex-col gap-3"
+ >
+ <EditToolbar info={info} id={id} setInfo={setInfo} />
+ <Button
+ onClick={async () => {
+ const res = await deleteCanvas(id);
+ if (res.success) {
+ toast.success("Thinkpad removed.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ setDeleted();
+ } else {
+ toast.warning("Something went wrong.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ }
+ }}
+ className="flex gap-2 border-border"
+ variant="outline"
+ >
+ <TrashIcon className="size-8 stroke-1" /> Delete
+ </Button>
+ </PopoverContent>
+ </Popover>
+ );
+}
+
+function EditToolbar({
+ id,
+ setInfo,
+ info,
+}: {
+ id: string;
+ setInfo: ({
+ title,
+ description,
+ }: {
+ title: string;
+ description: string;
+ }) => void;
+ info: {
+ title: string;
+ description: string;
+ };
+}) {
+ const [open, setOpen] = useState(false);
+ return (
+ <Dialog open={open} onOpenChange={setOpen}>
+ <DialogTrigger asChild>
+ <Button className="flex gap-2 border-border" variant="outline">
+ <PencilSquareIcon className="size-8 stroke-1" /> Edit
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="sm:max-w-[425px] bg-[#161f2a]/30 border-0">
+ <form
+ action={async (FormData) => {
+ const data = {
+ title: FormData.get("title") as string,
+ description: FormData.get("description") as string,
+ };
+ const res = await AddCanvasInfo({ id, ...data });
+ if (res.success) {
+ setOpen(false);
+ setInfo(data);
+ } else {
+ setOpen(false);
+ toast.error("Something went wrong.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ }
+ }}
+ >
+ <DialogHeader>
+ <DialogTitle>Edit Canvas</DialogTitle>
+ <DialogDescription>
+ Add Description to your canvas. Pro tip: Let AI do the job, as you
+ add your content into canvas, we will autogenerate your
+ description.
+ </DialogDescription>
+ </DialogHeader>
+ <div className="grid gap-4 py-4">
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label htmlFor="title" className="text-right">
+ Title
+ </Label>
+ <Input
+ defaultValue={info.title}
+ name="title"
+ id="title"
+ placeholder="life planning..."
+ className="col-span-3 border-0"
+ />
+ </div>
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label htmlFor="description" className="text-right">
+ Description
+ </Label>
+ <Textarea
+ defaultValue={info.description}
+ rows={6}
+ id="description"
+ name="description"
+ placeholder="contains information about..."
+ className="col-span-3 border-0 resize-none"
+ />
+ </div>
+ </div>
+ <DialogFooter>
+ <Button type="submit">Save changes</Button>
+ </DialogFooter>
+ </form>
+ </DialogContent>
+ </Dialog>
+ );
+}
+
+import { Suspense, memo, use, useState } from "react";
+import { Box, TldrawImage } from "tldraw";
+import { Button } from "@repo/ui/shadcn/button";
+import { AddCanvasInfo, deleteCanvas } from "@/app/actions/doers";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@repo/ui/shadcn/dialog";
+import { Input } from "@repo/ui/shadcn/input";
+import { Textarea } from "@repo/ui/shadcn/textarea";
+import { textCardUtil } from "@/components/canvas/textCard";
+import { twitterCardUtil } from "@/components/canvas/twitterCard";
+
+const ImageComponent = memo(({ id }: { id: string }) => {
+ const snapshot = use(getCanvasData(id));
+ if (snapshot.bounds) {
+ const pageBounds = new Box(
+ snapshot.bounds.x,
+ snapshot.bounds.y,
+ snapshot.bounds.w,
+ snapshot.bounds.h,
+ );
+
+ return (
+ <TldrawImage
+ shapeUtils={[twitterCardUtil, textCardUtil]}
+ snapshot={snapshot.snapshot}
+ background={false}
+ darkMode={true}
+ bounds={pageBounds}
+ padding={0}
+ scale={1}
+ format="png"
+ />
+ );
+ }
+ return (
+ <div className=" h-full w-full flex justify-center items-center">
+ Drew things to seee here
+ </div>
+ );
+});
diff --git a/apps/web/app/(canvas)/canvas/thinkPads.tsx b/apps/web/app/(canvas)/canvas/thinkPads.tsx
new file mode 100644
index 00000000..3e8d7550
--- /dev/null
+++ b/apps/web/app/(canvas)/canvas/thinkPads.tsx
@@ -0,0 +1,32 @@
+"use client";
+import { motion } from "framer-motion";
+import ThinkPad from "./thinkPad";
+
+const containerVariants = {
+ hidden: { opacity: 0 },
+ visible: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.1,
+ },
+ },
+};
+
+export default function ThinkPads({
+ data,
+}: {
+ data: { image: string; title: string; description: string; id: string }[];
+}) {
+ return (
+ <motion.div
+ variants={containerVariants}
+ initial="hidden"
+ animate="visible"
+ className="w-[90%] max-w-2xl space-y-6"
+ >
+ {data.map((item) => {
+ return <ThinkPad {...item} />;
+ })}
+ </motion.div>
+ );
+}
diff --git a/apps/web/app/(canvas)/canvasStyles.css b/apps/web/app/(canvas)/canvasStyles.css
index 04da2054..af4740e1 100644
--- a/apps/web/app/(canvas)/canvasStyles.css
+++ b/apps/web/app/(canvas)/canvasStyles.css
@@ -1,28 +1,36 @@
.tl-background {
- background: #1F2428 !important;
+ background: #1f2428 !important;
}
-.tlui-style-panel.tlui-style-panel__wrapper, .tlui-navigation-panel::before ,.tlui-menu-zone, .tlui-toolbar__tools, .tlui-popover__content, .tlui-menu, .tlui-button__help, .tlui-help-menu, .tlui-dialog__content {
- background: #2C3439 !important;
- border-top: #2C3439 !important;
- border-right: #2C3439 !important;
- border-bottom: #2C3439 !important;
- border-left: #2C3439 !important;
+.tlui-style-panel.tlui-style-panel__wrapper,
+.tlui-navigation-panel::before,
+.tlui-menu-zone,
+.tlui-toolbar__tools,
+.tlui-popover__content,
+.tlui-menu,
+.tlui-button__help,
+.tlui-help-menu,
+.tlui-dialog__content {
+ background: #2c3840 !important;
+ border-top: #2c3840 !important;
+ border-right: #2c3840 !important;
+ border-bottom: #2c3840 !important;
+ border-left: #2c3840 !important;
}
.tlui-navigation-panel::before {
- border-top: #2C3439 !important;
- border-right: #2C3439 !important;
+ border-top: #2c3840 !important;
+ border-right: #2c3840 !important;
}
.tlui-minimap {
- background: #2C3439 !important;
+ background: #2c3840 !important;
}
.tlui-minimap__canvas {
- background: #1F2428 !important;
+ background: #1f2428 !important;
}
.tlui-dialog__overlay {
position: fixed;
-} \ No newline at end of file
+}
diff --git a/apps/web/app/(canvas)/layout.tsx b/apps/web/app/(canvas)/layout.tsx
new file mode 100644
index 00000000..33e0adfb
--- /dev/null
+++ b/apps/web/app/(canvas)/layout.tsx
@@ -0,0 +1,30 @@
+import { auth } from "@/server/auth";
+import "./canvasStyles.css";
+import { redirect } from "next/navigation";
+import BackgroundPlus from "../(landing)/GridPatterns/PlusGrid";
+import { Toaster } from "@repo/ui/shadcn/sonner";
+
+export default async function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const info = await auth();
+
+ if (!info) {
+ return redirect("/signin");
+ }
+ return (
+ <>
+ <div className="relative flex justify-center z-40 pointer-events-none">
+ <div
+ className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ <BackgroundPlus className="absolute top-0 left-0 w-full h-full -z-50 opacity-70" />
+ <div>{children}</div>
+ <Toaster />
+ </>
+ );
+}