mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-05-19 07:42:43 +00:00
commit
38565f2ec4
12 changed files with 397 additions and 73 deletions
|
|
@ -6,10 +6,9 @@ import ThinkPads from "./thinkPads";
|
|||
async function page() {
|
||||
const canvas = await getCanvas();
|
||||
return (
|
||||
<div className="h-screen w-full bg-[#171B1F] py-32 text-[#FFFFFF] ">
|
||||
<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>
|
||||
<p>{JSON.stringify(canvas)}</p>
|
||||
<SearchandCreate />
|
||||
{
|
||||
// @ts-ignore
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
"use client"
|
||||
"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 (
|
||||
|
|
@ -18,18 +19,27 @@ export default function SearchandCreate() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<form action={createCanvas}>
|
||||
<Button />
|
||||
<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()
|
||||
const { pending } = useFormStatus();
|
||||
return (
|
||||
<button className="rounded-xl bg-[#1F2428] px-5 py-3 text-xl text-[#B8C4C6]">
|
||||
{pending? "Creating.." : "Create New"}
|
||||
{pending ? "Creating.." : "Create New"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import {motion} from "framer-motion"
|
||||
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)" },
|
||||
|
|
@ -10,27 +18,259 @@ export default function ThinkPad({
|
|||
title,
|
||||
description,
|
||||
image,
|
||||
id
|
||||
id,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
id: string;
|
||||
}) {
|
||||
const [deleted, setDeleted] = useState(false);
|
||||
const [info, setInfo] = useState({ title, description });
|
||||
return (
|
||||
<motion.div
|
||||
variants={childVariants}
|
||||
className="flex h-48 gap-4 rounded-2xl bg-[#1F2428] p-2"
|
||||
>
|
||||
<Link className="h-full min-w-[40%] rounded-xl bg-[#363f46]" href={`/canvas/${id}`}>
|
||||
<div></div>
|
||||
</Link>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>{title}</div>
|
||||
<div className="overflow-hidden text-ellipsis text-[#B8C4C6]">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
<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>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.tl-background {
|
||||
background: #1F2428 !important;
|
||||
background: #181E23 !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 {
|
||||
|
|
|
|||
|
|
@ -1,19 +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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -391,6 +391,12 @@ export const createCanvas = async () => {
|
|||
return { error: "Not authenticated", success: false };
|
||||
}
|
||||
|
||||
const canvases = await db.select().from(canvas).where(eq(canvas.userId, data.user.id))
|
||||
|
||||
if (canvases.length >= 5){
|
||||
return {success: false, message: "A user currently can only have 5 canvases"}
|
||||
}
|
||||
|
||||
const resp = await db
|
||||
.insert(canvas)
|
||||
.values({ userId: data.user.id }).returning({id: canvas.id});
|
||||
|
|
@ -428,4 +434,33 @@ export const SaveCanvas = async ({id, data}: {id: string, data: string}) => {
|
|||
} catch (error) {
|
||||
return {success: false, error, message:"An error occured while saving your canvas"}
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteCanvas = async (id: string) => {
|
||||
try {
|
||||
await process.env.CANVAS_SNAPS.delete(id)
|
||||
await db.delete(canvas).where(eq(canvas.id,id))
|
||||
return {
|
||||
success: true,
|
||||
message: "in-sync"
|
||||
}
|
||||
} catch (error) {
|
||||
return {success: false, error, message:"An error occured while saving your canvas"}
|
||||
}
|
||||
}
|
||||
|
||||
export async function AddCanvasInfo({id, title, description}: {id: string, title: string, description: string}){
|
||||
try {
|
||||
await db.update(canvas).set({description, title}).where(eq(canvas.id, id))
|
||||
return {
|
||||
success: true,
|
||||
message: "info updated successfully"
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: "something went wrong :/"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -251,7 +251,7 @@ export const getCanvasData = async (canvasId: string) => {
|
|||
if (canvas){
|
||||
return JSON.parse(canvas);
|
||||
} else {
|
||||
return {}
|
||||
return {snapshot: {}}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
27
apps/web/app/api/canvasai/route.ts
Normal file
27
apps/web/app/api/canvasai/route.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import type { NextRequest } from "next/server";
|
||||
import { ensureAuth } from "../ensureAuth";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const session = await ensureAuth(request);
|
||||
if (!session) {
|
||||
return new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
const res : {query: string} = await request.json()
|
||||
|
||||
try {
|
||||
const resp = await fetch(`${process.env.BACKEND_BASE_URL}/api/search?query=${res.query}&user=${session.user.id}`);
|
||||
if (resp.status !== 200 || !resp.ok) {
|
||||
const errorData = await resp.text();
|
||||
console.log(errorData);
|
||||
return new Response(
|
||||
JSON.stringify({ message: "Error in CF function", error: errorData }),
|
||||
{ status: resp.status },
|
||||
);
|
||||
}
|
||||
return new Response(JSON.stringify({response:await resp.json(), status: 200 }));
|
||||
} catch (error) {
|
||||
return new Response(`Error, ${error}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,19 @@
|
|||
import Image from "next/image";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
interface DraggableComponentsProps {
|
||||
content: string;
|
||||
extraInfo?: string;
|
||||
iconAlt: string;
|
||||
}
|
||||
import {motion} from "framer-motion"
|
||||
|
||||
export default function DraggableComponentsContainer({
|
||||
content,
|
||||
}: {
|
||||
content: DraggableComponentsProps[];
|
||||
content: {context:string}[] | undefined;
|
||||
}) {
|
||||
if (content === undefined) return null;
|
||||
return (
|
||||
<div className="flex flex-col gap-10">
|
||||
{content.map((i) => {
|
||||
return (
|
||||
<DraggableComponents
|
||||
content={i.content}
|
||||
iconAlt={i.iconAlt}
|
||||
extraInfo={i.extraInfo}
|
||||
content={i.context}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
@ -29,9 +23,7 @@ export default function DraggableComponentsContainer({
|
|||
|
||||
function DraggableComponents({
|
||||
content,
|
||||
extraInfo,
|
||||
iconAlt,
|
||||
}: DraggableComponentsProps) {
|
||||
}: {content: string}) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -49,19 +41,21 @@ function DraggableComponents({
|
|||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: 5}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
ref={containerRef}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragStart={handleDragStart}
|
||||
draggable
|
||||
className={`flex gap-4 px-1 rounded-md text-[#989EA4] border-2 transition ${isDragging ? "border-blue-600" : "border-[#1F2428]"}`}
|
||||
className={`flex gap-4 px-3 overflow-hidden rounded-md text-[#989EA4] border-2 transition ${isDragging ? "border-blue-600" : "border-[#1F2428]"}`}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>
|
||||
<h1 className="line-clamp-3">{content}</h1>
|
||||
</div>
|
||||
<p className="line-clamp-1 text-[#369DFD]">{extraInfo}</p>
|
||||
{/* <p className="line-clamp-1 text-[#369DFD]">{extraInfo}</p> */}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,18 @@ interface RectContextType {
|
|||
setFullScreen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
visible: boolean;
|
||||
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
id: string
|
||||
id: string;
|
||||
}
|
||||
|
||||
const RectContext = createContext<RectContextType | undefined>(undefined);
|
||||
|
||||
export const RectProvider = ({ id, children }: {id: string, children: React.ReactNode}) => {
|
||||
export const RectProvider = ({
|
||||
id,
|
||||
children,
|
||||
}: {
|
||||
id: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [fullScreen, setFullScreen] = useState(false);
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
|
|
@ -36,12 +42,11 @@ export const RectProvider = ({ id, children }: {id: string, children: React.Reac
|
|||
export const useRect = () => {
|
||||
const context = useContext(RectContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useRect must be used within a RectProvider');
|
||||
throw new Error("useRect must be used within a RectProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
export function ResizaleLayout() {
|
||||
const { setVisible, fullScreen, setFullScreen } = useRect();
|
||||
|
||||
|
|
@ -82,7 +87,7 @@ export function ResizaleLayout() {
|
|||
}
|
||||
|
||||
function DragIconContainer() {
|
||||
const { fullScreen} = useRect();
|
||||
const { fullScreen } = useRect();
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg bg-[#2F363B] ${!fullScreen && "px-1"} transition-all py-2`}
|
||||
|
|
@ -93,7 +98,7 @@ function DragIconContainer() {
|
|||
}
|
||||
|
||||
function CanvasContainer() {
|
||||
const { fullScreen} = useRect();
|
||||
const { fullScreen } = useRect();
|
||||
return (
|
||||
<div
|
||||
className={`absolute overflow-hidden transition-all inset-0 ${fullScreen ? "h-screen " : "h-[calc(100vh-3rem)] rounded-2xl"} w-full`}
|
||||
|
|
@ -104,7 +109,7 @@ function CanvasContainer() {
|
|||
}
|
||||
|
||||
function SidePanelContainer() {
|
||||
const { fullScreen, visible} = useRect();
|
||||
const { fullScreen, visible } = useRect();
|
||||
return (
|
||||
<div
|
||||
className={`flex transition-all rounded-2xl ${fullScreen ? "h-screen" : "h-[calc(100vh-3rem)]"} w-full flex-col overflow-hidden bg-[#1F2428]`}
|
||||
|
|
@ -123,35 +128,35 @@ function SidePanelContainer() {
|
|||
}
|
||||
|
||||
function SidePanel() {
|
||||
const [value, setValue] = useState("");
|
||||
// const [dragAsText, setDragAsText] = useState(false);
|
||||
const [content, setContent] = useState<{context: string}[]>()
|
||||
return (
|
||||
<>
|
||||
<div className="px-3 py-5">
|
||||
<input
|
||||
placeholder="search..."
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value);
|
||||
<form
|
||||
action={async (FormData) => {
|
||||
const search = FormData.get("search");
|
||||
console.log(search)
|
||||
const res = await fetch("/api/canvasai", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ query: search }),
|
||||
});
|
||||
const t = await res.json()
|
||||
console.log(t.response.response);
|
||||
setContent(t.response.response)
|
||||
}}
|
||||
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>
|
||||
>
|
||||
<input
|
||||
placeholder="search..."
|
||||
name="search"
|
||||
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"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<DraggableComponentsContainer content={content} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const content = [
|
||||
{
|
||||
content:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ export function SaveStatus({id}: {id:string}) {
|
|||
const debouncedSave = useCallback(
|
||||
debounce(async () => {
|
||||
const snapshot = getSnapshot(editor.store)
|
||||
SaveCanvas({id, data: JSON.stringify(snapshot)})
|
||||
const bounds = editor.getViewportPageBounds()
|
||||
console.log(bounds)
|
||||
|
||||
SaveCanvas({id, data: JSON.stringify({snapshot, bounds})})
|
||||
|
||||
setSave("saved!");
|
||||
}, 3000),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ export async function loadRemoteSnapshot(id:string) {
|
|||
const newStore = createTLStore({
|
||||
shapeUtils: [...defaultShapeUtils, twitterCardUtil, textCardUtil],
|
||||
});
|
||||
loadSnapshot(newStore, snapshot);
|
||||
loadSnapshot(newStore, snapshot.snapshot);
|
||||
return newStore;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue