let's go boys!! canvas

This commit is contained in:
codetorso 2024-07-25 10:56:32 +05:30
parent 3c22a45df3
commit c7b98a39b8
240 changed files with 1212 additions and 12199 deletions

View file

@ -1,22 +0,0 @@
import React from "react";
import { getCanvas } from "@/app/actions/fetchers";
import SearchandCreate from "./search&create";
import ThinkPads from "./thinkPads";
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;

View file

@ -1,45 +0,0 @@
"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>
);
}

View file

@ -1,276 +0,0 @@
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>
);
});

View file

@ -1,32 +0,0 @@
"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>
);
}

View file

@ -1,30 +0,0 @@
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 />
</>
);
}

View file

@ -62,6 +62,12 @@ function Menu() {
url: "/memories",
disabled: false,
},
{
icon: CanvasIcon,
text: "Thinkpad",
url: "/thinkpad",
disabled: false,
},
];
const [content, setContent] = useState("");
@ -76,9 +82,7 @@ function Menu() {
content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/)
) {
return "tweet";
} else if (content.match(/https?:\/\/[\w\.]+/)) {
return "page";
} else if (content.match(/https?:\/\/www\.[\w\.]+/)) {
} else if (content.match(/^(https?:\/\/)?(www\.)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(\/.*)?$/i)) {
return "page";
} else {
return "note";

View file

@ -1,3 +1,5 @@
@import url('tldraw/tldraw.css');
.tl-background {
background: #1f2428 !important;
}

View file

@ -0,0 +1,22 @@
import { auth } from "@/server/auth";
import "./canvasStyles.css";
import { redirect } from "next/navigation";
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="h-screen">
<div>{children}</div>
<Toaster />
</div>
);
}

View file

@ -1,17 +1,10 @@
import { userHasCanvas } from "@/app/actions/fetchers";
import {
RectProvider,
ResizaleLayout,
} from "@/components/canvas/resizableLayout";
import ResizableLayout 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");
redirect("/thinkpad");
}
return (
<RectProvider id={params.id}>
<ResizaleLayout />
</RectProvider>
);
return <ResizableLayout id={params.id} />;
}

View file

@ -0,0 +1,47 @@
"use client";
import { getCanvasData } from "@/app/actions/fetchers";
import { twitterCardUtil } from "@/components/canvas/custom_nodes/twittercard";
import { textCardUtil } from "@/components/canvas/custom_nodes/textcard";
import { memo, useEffect, useState } from "react";
import { Box, TldrawImage } from "tldraw";
const ImageComponent = memo(({ id }: { id: string }) => {
const [snapshot, setSnapshot] = useState({});
useEffect(() => {
(async () => {
setSnapshot(await 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="svg"
/>
);
}
return (
<div className="w-full aspect-video bg-[#2C3439] flex justify-center items-center">
Drew things to seee here
</div>
);
});
export default ImageComponent;

View file

@ -0,0 +1,115 @@
import { createCanvas } from "@/app/actions/doers";
import { getCanvas, getCanvasData } from "@/app/actions/fetchers";
import Link from "next/link";
import React from "react";
import ImageComponent from "./image";
import Menu from "@/app/(dash)/menu";
import Header from "@/app/(dash)/header/header";
import BackgroundPlus from "@/app/(landing)/GridPatterns/PlusGrid";
async function page() {
const canvas = await getCanvas();
return (
<div className="max-w-2xl m-auto pt-[20vh]">
<div className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl">
<span>Your</span>{" "}
<span className="inline-flex items-center gap-2 bg-gradient-to-r to-blue-300 from-zinc-300 text-transparent bg-clip-text">
ThinkPads
</span>
</div>
<BlurHeaderMenu />
<div className="w-full flex py-20">
{!canvas.success || canvas.error ? (
<div>Hmmm... Something went wrong. :/</div>
) : (
canvas.data &&
(canvas.data.length ? (
canvas.data.map((v) => (
<Canvas description={v.description} title={v.title} id={v.id} />
))
) : (
<CreateCanvas />
))
)}
</div>
<h3 className="fixed left-1/2 -translate-x-1/2 bottom-4 text-gray-400 pt-20 text-center">
*this is under beta and only one canvas is allowed per user
</h3>
</div>
);
}
function BlurHeaderMenu() {
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 className="fixed top-0 left-0 w-full z-40">
<Header />
</div>
<Menu />
</>
);
}
type TcanvasInfo = {
title: string;
description: string;
id: string;
};
function CreateCanvas() {
return (
<form action={createCanvas}>
<button
type="submit"
className="bg-secondary w-72 border-2 border-border rounded-md shadow-md shadow-[#1d1d1dc7] hover:scale-[1.03] active:scale-95"
>
<div className="w-full aspect-video bg-[#2C3439]"></div>
<div className="p-2 text-left">
<h2 className="text-lg text-gray-100">Unleash your creativity!</h2>
<h3 className="text-base text-gray-300">
This description will fill itself as you draw on the canvas
</h3>
</div>
</button>
</form>
);
}
function Canvas(props: TcanvasInfo) {
const { title, description, id } = props;
return (
<Link
href={`/thinkpad/${id}`}
className="bg-secondary w-72 border-2 border-border rounded-md shadow-md shadow-[#1d1d1dc7]"
>
<div className="w-full aspect-video bg-[#2C3439]">
<ImageComponent id={id} />
</div>
<div className="p-2 text-left">
<h2 className="text-lg text-gray-100">
{title === "Untitled" ? "Unleash your creativity!" : title}
</h2>
<h3 className="text-base text-gray-300">
{description === "Untitled"
? "This description will fill itself as you draw on the canvas"
: description}
</h3>
</div>
</Link>
);
}
export default page;

View file

@ -93,9 +93,7 @@ const typeDecider = (content: string) => {
// do strict checking with regex
if (content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/)) {
return "tweet";
} else if (content.match(/https?:\/\/[\w\.]+/)) {
return "page";
} else if (content.match(/https?:\/\/www\.[\w\.]+/)) {
} else if (content.match(/^(https?:\/\/)?(www\.)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(\/.*)?$/i)) {
return "page";
} else {
return "note";
@ -648,7 +646,7 @@ export const createCanvas = async () => {
.insert(canvas)
.values({ userId: data.user.id })
.returning({ id: canvas.id });
redirect(`/canvas/${resp[0]!.id}`);
redirect(`/thinkpad/${resp[0]!.id}`);
// TODO INVESTIGATE: NO REDIRECT INSIDE TRY CATCH BLOCK
// try {
// const resp = await db

View file

@ -22,6 +22,7 @@ import { ChatHistory, SourceZod } from "@repo/shared-types";
import { z } from "zod";
import { redirect } from "next/navigation";
import { cookies, headers } from "next/headers";
import { unfurl } from "@/lib/unfirlsite";
export const getUser = async (): ServerActionReturnType<User> => {
const data = await auth();
@ -377,3 +378,8 @@ export const getCanvasData = async (canvasId: string) => {
return { snapshot: {} };
}
};
export async function unfirlSite(website: string){
const data = await unfurl(website)
return data;
}

View file

@ -1,31 +1,32 @@
import type { NextRequest } from "next/server";
import { ensureAuth } from "../ensureAuth";
import { SourcesFromApi } from "@repo/shared-types";
export const runtime = "edge";
export async function POST(request: NextRequest) {
const session = await ensureAuth(request);
export async function POST(req: NextRequest) {
const session = await ensureAuth(req);
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 },
);
const res: { query: string } = await req.json();
const response = await fetch(
`${process.env.BACKEND_BASE_URL}/api/chat?query=${res.query}&user=${session.user.id}&sourcesOnly=true`,
{
headers: {
Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`,
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({})
}
return new Response(
JSON.stringify({ response: await resp.json(), status: 200 }),
);
} catch (error) {
return new Response(`Error, ${error}`);
}
}
)
const data = (await response.json()) as SourcesFromApi;
console.log(data);
return new Response(JSON.stringify(data), { status: 200 });
}

View file

@ -1,156 +0,0 @@
import { load } from "cheerio";
import { AwsClient } from "aws4fetch";
import type { NextRequest } from "next/server";
import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function POST(request: NextRequest) {
const r2 = new AwsClient({
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
});
async function unfurl(url: string) {
const response = await fetch(url);
if (response.status >= 400) {
throw new Error(`Error fetching url: ${response.status}`);
}
const contentType = response.headers.get("content-type");
if (!contentType?.includes("text/html")) {
throw new Error(`Content-type not right: ${contentType}`);
}
const content = await response.text();
const $ = load(content);
const og: { [key: string]: string | undefined } = {};
const twitter: { [key: string]: string | undefined } = {};
$("meta[property^=og:]").each(
// @ts-ignore, it just works so why care of type safety if someone has better way go ahead
(_, el) => (og[$(el).attr("property")!] = $(el).attr("content")),
);
$("meta[name^=twitter:]").each(
// @ts-ignore
(_, el) => (twitter[$(el).attr("name")!] = $(el).attr("content")),
);
const title =
og["og:title"] ??
twitter["twitter:title"] ??
$("title").text() ??
undefined;
const description =
og["og:description"] ??
twitter["twitter:description"] ??
$('meta[name="description"]').attr("content") ??
undefined;
const image =
og["og:image:secure_url"] ??
og["og:image"] ??
twitter["twitter:image"] ??
undefined;
return {
title,
description,
image,
};
}
const d = await ensureAuth(request);
if (!d) {
return new Response("Unauthorized", { status: 401 });
}
if (
!process.env.R2_ACCESS_KEY_ID ||
!process.env.R2_ACCOUNT_ID ||
!process.env.R2_SECRET_ACCESS_KEY ||
!process.env.R2_BUCKET_NAME
) {
return new Response(
"Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.",
{ status: 500 },
);
}
const website = new URL(request.url).searchParams.get("website");
if (!website) {
return new Response("Missing website", { status: 400 });
}
const salt = () => Math.floor(Math.random() * 11);
const encodeWebsite = `${encodeURIComponent(website)}${salt()}`;
try {
// this returns the og image, description and title of website
const response = await unfurl(website);
if (!response.image) {
return new Response(JSON.stringify(response));
}
if (!process.env.DEV_IMAGES) {
return new Response("Missing DEV_IMAGES namespace.", { status: 500 });
}
const imageUrl = await process.env.DEV_IMAGES!.get(encodeWebsite);
if (imageUrl) {
return new Response(
JSON.stringify({
image: imageUrl,
title: response.title,
description: response.description,
}),
);
}
const res = await fetch(`${response.image}`);
const image = await res.blob();
const url = new URL(
`https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
);
url.pathname = encodeWebsite;
url.searchParams.set("X-Amz-Expires", "3600");
const signedPuturl = await r2.sign(
new Request(url, {
method: "PUT",
}),
{
aws: { signQuery: true },
},
);
await fetch(signedPuturl.url, {
method: "PUT",
body: image,
});
await process.env.DEV_IMAGES.put(
encodeWebsite,
`${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
);
return new Response(
JSON.stringify({
image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
title: response.title,
description: response.description,
}),
);
} catch (error) {
console.log(error);
return new Response(
JSON.stringify({
status: 500,
error: error,
}),
);
}
}

View file

@ -1,96 +0,0 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { Editor, Tldraw, setUserPreferences, TLStoreWithStatus } from "tldraw";
import { createAssetFromUrl } from "../../lib/createAssetUrl";
import "tldraw/tldraw.css";
import { components } from "./enabledComp";
import { twitterCardUtil } from "./twitterCard";
import { textCardUtil } from "./textCard";
import createEmbedsFromUrl from "../../lib/createEmbeds";
import { loadRemoteSnapshot } from "../../lib/loadSnap";
import { SaveStatus } from "./savesnap";
import { getAssetUrls } from "@tldraw/assets/selfHosted";
import { memo } from "react";
import DragContext from "../../lib/context";
import DropZone from "./dropComponent";
import { useRect } from "./resizableLayout";
// import "./canvas.css";
export const Canvas = memo(() => {
const [isDraggingOver, setIsDraggingOver] = useState<boolean>(false);
const Dragref = useRef<HTMLDivElement | null>(null);
const handleDragOver = (event: any) => {
event.preventDefault();
setIsDraggingOver(true);
console.log("entere");
};
useEffect(() => {
const divElement = Dragref.current;
if (divElement) {
divElement.addEventListener("dragover", handleDragOver);
}
return () => {
if (divElement) {
divElement.removeEventListener("dragover", handleDragOver);
}
};
}, []);
return (
<DragContext.Provider value={{ isDraggingOver, setIsDraggingOver }}>
<div ref={Dragref} className="w-full h-full">
<TldrawComponent />
</div>
</DragContext.Provider>
);
});
const TldrawComponent = memo(() => {
const { id } = useRect();
const [storeWithStatus, setStoreWithStatus] = useState<TLStoreWithStatus>({
status: "loading",
});
useEffect(() => {
const fetchStore = async () => {
const store = await loadRemoteSnapshot(id);
setStoreWithStatus({
store: store,
status: "not-synced",
});
};
fetchStore();
}, []);
const handleMount = useCallback((editor: Editor) => {
(window as any).app = editor;
(window as any).editor = editor;
editor.registerExternalAssetHandler("url", createAssetFromUrl);
editor.registerExternalContentHandler("url", ({ url, point, sources }) => {
createEmbedsFromUrl({ url, point, sources, editor });
});
}, []);
setUserPreferences({ id: "supermemory", colorScheme: "dark" });
const assetUrls = getAssetUrls();
return (
<div className="w-full h-full">
<Tldraw
className="relative"
assetUrls={assetUrls}
components={components}
store={storeWithStatus}
shapeUtils={[twitterCardUtil, textCardUtil]}
onMount={handleMount}
>
<div className="absolute left-1/2 top-0 z-[1000000] flex -translate-x-1/2 gap-2 bg-[#2C3439] text-[#B3BCC5]">
<SaveStatus id={id} />
</div>
<DropZone />
</Tldraw>
</div>
);
});

View file

@ -0,0 +1,99 @@
import {
BaseBoxShapeUtil,
HTMLContainer,
TLBaseShape,
stopEventPropagation,
} from "tldraw";
type ITextCardShape = TLBaseShape<
"Textcard",
{ w: number; h: number; content: string; extrainfo: string; type: string }
>;
export class textCardUtil extends BaseBoxShapeUtil<ITextCardShape> {
static override type = "Textcard" as const;
getDefaultProps(): ITextCardShape["props"] {
return {
w: 100,
h: 50,
content: "",
extrainfo: "",
type: "",
};
}
override canEdit = () => true;
component(s: ITextCardShape) {
const isEditing = this.editor.getEditingShapeId() === s.id;
return (
<HTMLContainer
onPointerDown={isEditing ? stopEventPropagation : undefined}
className="flex h-full w-full items-center justify-center"
style={{
pointerEvents: isEditing ? "all" : "none",
}}
>
<div
className="overflow-hidden"
style={{
height: s.props.h,
width: s.props.w,
pointerEvents: "all",
background: "#232c2f",
borderRadius: "16px",
border: "2px solid #374151",
padding: "8px 14px",
}}
>
<h2 style={{ color: "#95A0AB" }}>{s.props.type}</h2>
{isEditing ? (
<input
value={s.props.content}
onChange={(e) =>
this.editor.updateShape<ITextCardShape>({
id: s.id,
type: "Textcard",
props: { content: e.currentTarget.value },
})
}
onPointerDown={(e) => {e.stopPropagation()}}
onTouchStart={(e) => {e.stopPropagation();}}
onTouchEnd={(e) => {e.stopPropagation();}}
className="bg-transparent block w-full text-lg font-medium border-[1px] border-[#556970]"
type="text"
/>
) : (
<h1 className="text-lg font-medium">{s.props.content}</h1>
)}
{isEditing ? (
<textarea
value={s.props.extrainfo}
onChange={(e) =>
this.editor.updateShape<ITextCardShape>({
id: s.id,
type: "Textcard",
props: { extrainfo: e.currentTarget.value },
})
}
onPointerDown={(e) => {e.stopPropagation();}}
onTouchStart={(e) => {e.stopPropagation()}}
onTouchEnd={(e) => {e.stopPropagation();}}
className="bg-transparent h-full w-full text-base font-medium border-[1px] border-[#556970]"
/>
) : (
<p style={{ fontSize: "15px", color: "#e5e7eb" }}>
{s.props.extrainfo}
</p>
)}
</div>
</HTMLContainer>
);
}
indicator(shape: ITextCardShape) {
return <rect width={shape.props.w} height={shape.props.h} />;
}
}

View file

@ -1,56 +0,0 @@
import Image from "next/image";
import { useRef, useState } from "react";
import { motion } from "framer-motion";
export default function DraggableComponentsContainer({
content,
}: {
content: { context: string }[] | undefined;
}) {
if (content === undefined) return null;
return (
<div className="flex flex-col gap-10">
{content.map((i) => {
return <DraggableComponents content={i.context} />;
})}
</div>
);
}
function DraggableComponents({ content }: { content: string }) {
const [isDragging, setIsDragging] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
setIsDragging(true);
if (containerRef.current) {
// Serialize the children as a string for dataTransfer
const childrenHtml = containerRef.current.innerHTML;
event.dataTransfer.setData("text/html", childrenHtml);
}
};
const handleDragEnd = () => {
setIsDragging(false);
};
return (
<motion.div
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
ref={containerRef}
onDragEnd={handleDragEnd}
// @ts-expect-error TODO: fix this
onDragStart={handleDragStart}
draggable
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> */}
</div>
</motion.div>
);
}

View file

@ -1,206 +0,0 @@
import React, { useRef, useCallback, useEffect, useContext } from "react";
import { useEditor } from "tldraw";
import DragContext, {
DragContextType,
useDragContext,
} from "../../lib/context";
import { handleExternalDroppedContent } from "../../lib/createEmbeds";
const stripHtmlTags = (html: string): string => {
const div = document.createElement("div");
div.innerHTML = html;
return div.textContent || div.innerText || "";
};
function formatTextToRatio(text: string) {
const totalWidth = text.length;
const maxLineWidth = Math.floor(totalWidth / 4);
const words = text.split(" ");
let lines = [];
let currentLine = "";
words.forEach((word) => {
// Check if adding the next word exceeds the maximum line width
if ((currentLine + word).length <= maxLineWidth) {
currentLine += (currentLine ? " " : "") + word;
} else {
// If the current line is full, push it to new line
lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) {
lines.push(currentLine);
}
return lines.join("\n");
}
function DropZone() {
const dropRef = useRef<HTMLDivElement | null>(null);
const { isDraggingOver, setIsDraggingOver } = useDragContext();
const editor = useEditor();
const handleDragLeave = () => {
setIsDraggingOver(false);
console.log("leaver");
};
useEffect(() => {
setInterval(() => {
editor.selectAll();
const shapes = editor.getSelectedShapes();
const text = shapes.filter((s) => s.type === "text");
console.log("hrhh", text);
}, 5000);
}, []);
const handleDrop = useCallback((event: DragEvent) => {
event.preventDefault();
setIsDraggingOver(false);
const dt = event.dataTransfer;
if (!dt) {
return;
}
const items = dt.items;
for (let i = 0; i < items.length; i++) {
if (items[i]!.kind === "file" && items[i]!.type.startsWith("image/")) {
const file = items[i]!.getAsFile();
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
if (e.target) {
// setDroppedImage(e.target.result as string);
}
};
reader.readAsDataURL(file);
}
} else if (items[i]!.kind === "string") {
items[i]!.getAsString((data) => {
const cleanText = stripHtmlTags(data);
const onethree = formatTextToRatio(cleanText);
handleExternalDroppedContent({ editor, text: onethree });
});
}
}
}, []);
useEffect(() => {
const divElement = dropRef.current;
if (divElement) {
divElement.addEventListener("drop", handleDrop);
divElement.addEventListener("dragleave", handleDragLeave);
}
return () => {
if (divElement) {
divElement.removeEventListener("drop", handleDrop);
divElement.addEventListener("dragleave", handleDragLeave);
}
};
}, []);
return (
<div
className={`h-full flex justify-center items-center w-full absolute top-0 left-0 z-[100000] pointer-events-none ${isDraggingOver && "bg-[#2c3439ad] pointer-events-auto"}`}
ref={dropRef}
>
{isDraggingOver && (
<>
<div className="absolute top-4 left-8">
<TopRight />
</div>
<div className="absolute top-4 right-8">
<TopLeft />
</div>
<div className="absolute bottom-4 left-8">
<BottomLeft />
</div>
<div className="absolute bottom-4 right-8">
<BottomRight />
</div>
<h2 className="text-2xl">Drop here to add Content on Canvas</h2>
</>
)}
</div>
);
}
function TopRight() {
return (
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M44 4H12C7.58172 4 4 7.58172 4 12V44"
stroke="white"
stroke-width="8"
stroke-linecap="round"
/>
</svg>
);
}
function TopLeft() {
return (
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4 4H36C40.4183 4 44 7.58172 44 12V44"
stroke="white"
stroke-width="8"
stroke-linecap="round"
/>
</svg>
);
}
function BottomLeft() {
return (
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M44 44H12C7.58172 44 4 40.4183 4 36V4"
stroke="white"
stroke-width="8"
stroke-linecap="round"
/>
</svg>
);
}
function BottomRight() {
return (
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4 44H36C40.4183 44 44 40.4183 44 36V4"
stroke="white"
stroke-width="8"
stroke-linecap="round"
/>
</svg>
);
}
export default DropZone;

View file

@ -19,4 +19,4 @@ export const components: Partial<TLUiComponents> = {
// HelperButtons: null,
// SharePanel: null,
// MenuPanel: null,
};
};

View file

@ -1,22 +0,0 @@
import { TLUiComponents } from "tldraw";
export const components: Partial<TLUiComponents> = {
ActionsMenu: null,
MainMenu: null,
QuickActions: null,
TopPanel: null,
DebugPanel: null,
DebugMenu: null,
PageMenu: null,
// Minimap: null,
// ContextMenu: null,
// HelpMenu: null,
// ZoomMenu: null,
// StylePanel: null,
// NavigationPanel: null,
// Toolbar: null,
// KeyboardShortcutsDialog: null,
// HelperButtons: null,
// SharePanel: null,
// MenuPanel: null,
};

View file

@ -1,177 +0,0 @@
"use client";
import { Canvas } from "./canvas";
import React, { createContext, useContext, useState } from "react";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import { SettingsIcon, DragIcon } from "@repo/ui/icons";
import DraggableComponentsContainer from "./draggableComponent";
import Image from "next/image";
import { Label } from "@repo/ui/shadcn/label";
interface RectContextType {
fullScreen: boolean;
setFullScreen: React.Dispatch<React.SetStateAction<boolean>>;
visible: boolean;
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
id: string;
}
const RectContext = createContext<RectContextType | undefined>(undefined);
export const RectProvider = ({
id,
children,
}: {
id: string;
children: React.ReactNode;
}) => {
const [fullScreen, setFullScreen] = useState(false);
const [visible, setVisible] = useState(true);
const value = {
id,
fullScreen,
setFullScreen,
visible,
setVisible,
};
return <RectContext.Provider value={value}>{children}</RectContext.Provider>;
};
export const useRect = () => {
const context = useContext(RectContext);
if (context === undefined) {
throw new Error("useRect must be used within a RectProvider");
}
return context;
};
export function ResizaleLayout() {
const { setVisible, fullScreen, setFullScreen } = useRect();
return (
<div
className={`h-screen w-full ${!fullScreen ? "px-4 py-6" : "bg-[#1F2428]"} transition-all`}
>
<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}
>
<SidePanelContainer />
</Panel>
<PanelResizeHandle
className={`relative flex items-center transition-all justify-center ${!fullScreen && "px-1"}`}
>
<DragIconContainer />
</PanelResizeHandle>
<Panel className="relative" defaultSize={70} minSize={60}>
<CanvasContainer />
</Panel>
</PanelGroup>
</div>
);
}
function DragIconContainer() {
const { fullScreen } = useRect();
return (
<div
className={`rounded-lg bg-[#2F363B] ${!fullScreen && "px-1"} transition-all py-2`}
>
<Image src={DragIcon} alt="drag-icon" />
</div>
);
}
function CanvasContainer() {
const { fullScreen } = useRect();
return (
<div
className={`absolute overflow-hidden transition-all inset-0 ${fullScreen ? "h-screen " : "h-[calc(100vh-3rem)] rounded-2xl"} w-full`}
>
<Canvas />
</div>
);
}
function SidePanelContainer() {
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]`}
>
<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>
);
}
function SidePanel() {
const [content, setContent] = useState<{ context: string }[]>();
return (
<>
<div className="px-3 py-5">
<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();
// @ts-expect-error TODO: fix this
console.log(t.response.response);
// @ts-expect-error TODO: fix this
setContent(t.response.response);
}}
>
<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:
"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.",
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)",
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",
},
];

View file

@ -0,0 +1,46 @@
"use client";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import TldrawComponent from "./tldrawComponent";
import Sidepanel from "./sidepanel";
import Image from "next/image";
import { DragIcon } from "@repo/ui/icons";
import { useRef, useState } from "react";
export default function ResizableLayout({ id }: { id: string }) {
const panelGroupRef = useRef(null);
const [isLeftPanelCollapsed, setIsLeftPanelCollapsed] = useState(false);
const handleResize = () => {
if (isLeftPanelCollapsed && panelGroupRef.current) {
panelGroupRef.current.setLayout([20, 80]);
}
};
return (
<PanelGroup
className="text-white h-screen py-[1vh] px-[1vh] bg-[#111417]"
direction="horizontal"
ref={panelGroupRef}
onLayout={(sizes) => {
setIsLeftPanelCollapsed(sizes[0] === 0);
}}
>
<Panel collapsible={true} defaultSize={30} minSize={20}>
<Sidepanel />
</Panel>
<PanelResizeHandle onClick={handleResize} className="w-4 h-[98vh] relative">
<div
className={`rounded-lg bg-[#2F363B] absolute top-1/2 -translate-y-1/2 px-1 transition-all py-2`}
>
<Image src={DragIcon} alt="drag-icon" />
</div>
</PanelResizeHandle>
<Panel defaultSize={70} minSize={60}>
<div className="relative w-full h-[98vh] rounded-xl overflow-hidden">
<TldrawComponent id={id} />
</div>
</Panel>
</PanelGroup>
);
}

View file

@ -16,7 +16,7 @@ export function SaveStatus({ id }: { id: string }) {
setSave("saved!");
}, 3000),
[editor], // Dependency array ensures the function is not recreated on every render
[editor], // ensures the function is not recreated on every render
);
useEffect(() => {
@ -28,7 +28,7 @@ export function SaveStatus({ id }: { id: string }) {
{ scope: "document", source: "user" },
);
return () => unsubscribe(); // Cleanup on unmount
return () => unsubscribe();
}, [editor, debouncedSave]);
return <button>{save}</button>;

View file

@ -0,0 +1,101 @@
import React, { useState } from "react";
import { ArrowLeftIcon, Cog6ToothIcon } from "@heroicons/react/16/solid";
import Link from "next/link";
import Card from "./sidepanelcard";
import { sourcesZod } from "@repo/shared-types";
import { toast } from "sonner";
type card = {
title: string;
type: string;
source: string;
content: string;
numChunks: string;
};
function Sidepanel() {
const [content, setContent] = useState<card[]>([]);
return (
<div className="h-[98vh] bg-[#1f2428] rounded-xl overflow-hidden">
<div className="flex justify-between bg-[#2C3439] items-center py-2 px-4 mb-2 text-lg">
<Link
href="/thinkpad"
className="p-2 px-4 transition-colors rounded-lg hover:bg-[#334044] flex items-center gap-2"
>
<ArrowLeftIcon className="h-5 w-5" />
Back
</Link>
<div className="p-2 px-4 transition-colors rounded-lg hover:bg-[#334044] flex items-center gap-2">
<Cog6ToothIcon className="h-5 w-5" />
Options
</div>
</div>
<div className="h-full px-2">
<div className=" p-2 h-full">
<Search setContent={setContent} />
<div className="py-5 space-y-4">
{content.map((v, i) => (
<Card {...v} />
))}
</div>
</div>
</div>
</div>
);
}
function Search({ setContent }: { setContent: (e: any) => void }) {
return (
<form
action={async (FormData) => {
const search = FormData.get("search") as string;
const sourcesFetch = await fetch("/api/canvasai", {
method: "POST",
body: JSON.stringify({ query: search }),
});
const sources = await sourcesFetch.json();
const sourcesParsed = sourcesZod.safeParse(sources);
if (!sourcesParsed.success) {
console.error(sourcesParsed.error);
toast.error("Something went wrong while getting the sources");
return;
}
const filteredSourceUrls = new Set(
sourcesParsed.data.metadata.map((source) => source.url),
);
const uniqueSources = sourcesParsed.data.metadata.filter((source) => {
if (filteredSourceUrls.has(source.url)) {
filteredSourceUrls.delete(source.url);
return true;
}
return false;
});
setContent(
uniqueSources.map((source) => ({
title: source.title ?? "Untitled",
type: source.type ?? "page",
source: source.url ?? "https://supermemory.ai",
content: source.description ?? "No content available",
numChunks: sourcesParsed.data.metadata.filter(
(f) => f.url === source.url,
).length,
})),
);
}}
>
<input
name="search"
placeholder="search memories..."
className="rounded-md w-full bg-[#121718] p-3 text-lg outline-none"
type="text"
/>
</form>
);
}
export default Sidepanel;

View file

@ -0,0 +1,69 @@
import { GlobeAltIcon } from "@heroicons/react/16/solid";
import { TwitterIcon, TypeIcon } from "lucide-react";
import { useState } from "react";
import { motion } from "framer-motion";
import useMeasure from "react-use-measure";
type CardType = string;
export default function Card({
type,
title,
content,
source,
}: {
type: CardType;
title: string;
content: string;
source?: string;
}) {
const [ref, bounds] = useMeasure();
const [isDragging, setIsDragging] = useState(false);
const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
setIsDragging(true);
event.dataTransfer.setData(
"application/json",
JSON.stringify({ type, title, content, source })
);
};
const handleDragEnd = () => {
setIsDragging(false);
};
return (
<motion.div animate={{height: bounds.height}}>
<div
draggable
onDragEnd={handleDragEnd}
onDragStart={handleDragStart}
className={`rounded-lg hover:scale-[1.02] group cursor-grab scale-[0.98] select-none transition-all border-[#232c2f] hover:bg-[#232c32] ${
isDragging ? "border-blue-600 border-dashed border-2" : ""
}`}
>
<div ref={ref} className="flex gap-4 px-3 py-2 items-center">
<a href={source} className={`${source && "cursor-pointer"}`}>
<Icon type={type} />
</a>
<div>
<h2>{title}</h2>
<p className="group-hover:line-clamp-[12] transition-all line-clamp-3 text-gray-200">
{content}
</p>
</div>
</div>
</div>
</motion.div>
);
}
function Icon({ type }: { type: CardType }) {
return type === "note" ? (
<TypeIcon />
) : type === "page" ? (
<GlobeAltIcon className="h-9 w-5" />
) : (
<TwitterIcon />
);
}

View file

@ -1,47 +0,0 @@
import { BaseBoxShapeUtil, HTMLContainer, TLBaseShape } from "tldraw";
type ITextCardShape = TLBaseShape<
"Textcard",
{ w: number; h: number; content: string; extrainfo: string }
>;
export class textCardUtil extends BaseBoxShapeUtil<ITextCardShape> {
static override type = "Textcard" as const;
getDefaultProps(): ITextCardShape["props"] {
return {
w: 100,
h: 50,
content: "",
extrainfo: "",
};
}
component(s: ITextCardShape) {
return (
<HTMLContainer className="flex h-full w-full items-center justify-center">
<div
style={{
height: s.props.h,
width: s.props.w,
pointerEvents: "all",
background: "#2E3C4C",
borderRadius: "16px",
border: "2px solid #3e4449",
padding: "8px 14px",
overflow: "auto",
}}
>
<h1 style={{ fontSize: "15px" }}>{s.props.content}</h1>
<p style={{ fontSize: "14px", color: "#369DFD" }}>
{s.props.extrainfo}
</p>
</div>
</HTMLContainer>
);
}
indicator(shape: ITextCardShape) {
return <rect width={shape.props.w} height={shape.props.h} />;
}
}

View file

@ -0,0 +1,84 @@
import React, {
createContext,
memo,
useCallback,
useEffect,
useState,
} from "react";
import { Editor, TLStoreWithStatus, Tldraw, setUserPreferences } from "tldraw";
import { components } from "./enabled";
import { twitterCardUtil } from "./custom_nodes/twittercard";
import { textCardUtil } from "./custom_nodes/textcard";
import DropZone from "./tldrawDrop";
import { loadRemoteSnapshot } from "@/lib/loadSnap";
import { createAssetFromUrl } from "@/lib/createAssetUrl";
import createEmbedsFromUrl from "@/lib/ExternalDroppedContent";
import { getAssetUrls } from "@tldraw/assets/selfHosted";
import { SaveStatus } from "./savesnap";
interface DragContextType {
isDraggingOver: boolean;
setIsDraggingOver: React.Dispatch<React.SetStateAction<boolean>>;
}
export const DragContext = createContext<DragContextType | undefined>(
undefined,
);
function TldrawComponent({ id }: { id: string }) {
const [isDraggingOver, setIsDraggingOver] = useState<boolean>(false);
return (
<DragContext.Provider value={{ isDraggingOver, setIsDraggingOver }}>
<div className="h-[98vh]" onDragOver={() => setIsDraggingOver(true)}>
<Thinkpad id={id} />
</div>
</DragContext.Provider>
);
}
export const Thinkpad = memo(({ id }: { id: string }) => {
const [storeWithStatus, setStoreWithStatus] = useState<TLStoreWithStatus>({
status: "loading",
});
useEffect(() => {
const fetchStore = async () => {
const store = await loadRemoteSnapshot(id);
setStoreWithStatus({
store: store,
status: "not-synced",
});
};
fetchStore();
}, []);
const handleMount = useCallback((editor: Editor) => {
(window as any).app = editor;
(window as any).editor = editor;
editor.registerExternalAssetHandler("url", createAssetFromUrl);
editor.registerExternalContentHandler("url", ({ url, point, sources }) => {
createEmbedsFromUrl({ url, point, sources, editor });
});
}, []);
setUserPreferences({ id: "supermemory", colorScheme: "dark" });
return (
<Tldraw
className="relative"
components={components}
store={storeWithStatus}
shapeUtils={[twitterCardUtil, textCardUtil]}
onMount={handleMount}
>
<DropZone />
<div className="absolute left-1/2 top-0 z-[1000000] flex -translate-x-1/2 gap-2 bg-[#2C3439] text-[#B3BCC5]">
<SaveStatus id={id} />
</div>
</Tldraw>
);
});
export default TldrawComponent;

View file

@ -0,0 +1,67 @@
import { handleExternalDroppedContent } from "@/lib/ExternalDroppedContent";
import { BomttomLeftIcon, BomttomRightIcon, TopLeftIcon, TopRightIcon } from "@repo/ui/icons";
import Image from "next/image";
import { useContext } from "react";
import { useEditor } from "tldraw";
import { DragContext } from "./tldrawComponent";
type CardData = {
type: any; // Adjust this according to your actual type
title: string;
content: string;
url: string;
};
function DropZone() {
const editor = useEditor();
const dragContext = useContext(DragContext);
if (!dragContext) {
throw new Error("Thinkpad must be used within a DragContextProvider");
}
const { isDraggingOver, setIsDraggingOver } = dragContext;
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
const data = event.dataTransfer.getData("application/json");
try {
const cardData: CardData = JSON.parse(data);
console.log("drop", cardData);
handleExternalDroppedContent({ editor, droppedData: cardData });
} catch (e) {
const textData = event.dataTransfer.getData("text/plain");
handleExternalDroppedContent({ editor, droppedData: textData });
}
setIsDraggingOver(false);
};
return (
<div
onDrop={handleDrop}
onDragOver={(e) => e.preventDefault()}
onDragLeave={() => setIsDraggingOver(false)}
className={`w-full absolute ${
isDraggingOver ? "z-[500]" : "z-[100] pointer-events-none"
} rounded-lg h-full flex items-center justify-center`}
>
{isDraggingOver && (
<>
<div className="absolute top-4 left-8">
<Image src={TopRightIcon} alt="" />
</div>
<div className="absolute top-4 right-8">
<Image src={TopLeftIcon} alt="" />
</div>
<div className="absolute bottom-4 left-8">
<Image src={BomttomLeftIcon} alt="" />
</div>
<div className="absolute bottom-4 right-8">
<Image src={BomttomRightIcon} alt="" />
</div>
</>
)}
</div>
);
}
export default DropZone;

View file

@ -0,0 +1,284 @@
import {
AssetRecordType,
Editor,
TLAsset,
TLAssetId,
TLBookmarkShape,
TLExternalContentSource,
TLShapePartial,
Vec,
VecLike,
createShapeId,
getEmbedInfo,
getHashForString,
} from "tldraw";
import { unfirlSite } from "@/app/actions/fetchers";
export default async function createEmbedsFromUrl({
url,
point,
sources,
editor,
}: {
url: string;
point?: VecLike | undefined;
sources?: TLExternalContentSource[] | undefined;
editor: Editor;
}) {
const position =
point ??
(editor.inputs.shiftKey
? editor.inputs.currentPagePoint
: editor.getViewportPageBounds().center);
const urlPattern = /https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/;
if (urlPattern.test(url)) {
return editor.createShape({
type: "Twittercard",
x: position.x - 250,
y: position.y - 150,
props: { url: url },
});
}
// try to paste as an embed first
const embedInfo = getEmbedInfo(url);
if (embedInfo) {
return editor.putExternalContent({
type: "embed",
url: embedInfo.url,
point,
embed: embedInfo.definition,
});
}
const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url));
const shape = createEmptyBookmarkShape(editor, url, position);
let asset = editor.getAsset(assetId) as TLAsset;
let shouldAlsoCreateAsset = false;
if (!asset) {
shouldAlsoCreateAsset = true;
try {
const bookmarkAsset = await editor.getAssetForExternalContent({
type: "url",
url,
});
const value = await unfirlSite(url);
if (bookmarkAsset) {
if (bookmarkAsset.type === "bookmark" ){
if (value.title ) bookmarkAsset.props.title = value.title;
if (value.image) bookmarkAsset.props.image = value.image;
if (value.description)
bookmarkAsset.props.description = value.description;
}
}
if (!bookmarkAsset) throw Error("Could not create an asset");
asset = bookmarkAsset;
} catch (e) {
console.log(e);
return;
}
}
editor.batch(() => {
if (shouldAlsoCreateAsset) {
editor.createAssets([asset]);
}
editor.updateShapes([
{
id: shape.id,
type: shape.type,
props: {
assetId: asset.id,
},
},
]);
});
}
function processURL(input: string): string | null {
let str = input.trim();
if (!/^(?:f|ht)tps?:\/\//i.test(str)) {
str = "http://" + str;
}
try {
const url = new URL(str);
return url.href;
} catch {
return str.match(
/^(https?:\/\/)?(www\.)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(\/.*)?$/i
)
? str
: null;
}
}
function formatTextToRatio(text: string): { height: number; width: number } {
const RATIO = 4 / 3;
const FONT_SIZE = 15;
const CHAR_WIDTH = FONT_SIZE * 0.6;
const LINE_HEIGHT = FONT_SIZE * 1.2;
const MIN_WIDTH = 200;
let width = Math.min(
800,
Math.max(MIN_WIDTH, Math.ceil(text.length * CHAR_WIDTH))
);
width = Math.ceil(width / 4) * 4;
const maxLineWidth = Math.floor(width / CHAR_WIDTH);
const words = text.split(" ");
let lines: string[] = [];
let currentLine = "";
words.forEach((word) => {
if ((currentLine + word).length <= maxLineWidth) {
currentLine += (currentLine ? " " : "") + word;
} else {
lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) {
lines.push(currentLine);
}
let height = Math.ceil(lines.length * LINE_HEIGHT);
if (width / height > RATIO) {
width = Math.ceil(height * RATIO);
} else {
height = Math.ceil(width / RATIO);
}
return { height, width };
}
type CardData = {
type: string;
title: string;
content: string;
url: string;
};
type DroppedData = CardData | string | { imageUrl: string };
export function handleExternalDroppedContent({
droppedData,
editor,
}: {
droppedData: DroppedData;
editor: Editor;
}) {
const position = editor.inputs.shiftKey
? editor.inputs.currentPagePoint
: editor.getViewportPageBounds().center;
if (typeof droppedData === "string") {
const processedURL = processURL(droppedData);
if (processedURL) {
createEmbedsFromUrl({ editor, url: processedURL });
return;
} else {
const { height, width } = formatTextToRatio(droppedData);
editor.createShape({
type: "Textcard",
x: position.x - width / 2,
y: position.y - height / 2,
props: {
content: "",
extrainfo: droppedData,
type: "note",
w: 300,
h: 200,
},
});
}
} else if ("imageUrl" in droppedData) {
} else {
const { content, title, url, type } = droppedData;
const processedURL = processURL(url);
if (processedURL) {
createEmbedsFromUrl({ editor, url: processedURL });
return;
}
const { height, width } = formatTextToRatio(content);
editor.createShape({
type: "Textcard",
x: position.x - 250,
y: position.y - 150,
props: {
type,
content: title,
extrainfo: content,
w: height,
h: width,
},
});
}
}
function centerSelectionAroundPoint(editor: Editor, position: VecLike) {
// Re-position shapes so that the center of the group is at the provided point
const viewportPageBounds = editor.getViewportPageBounds();
let selectionPageBounds = editor.getSelectionPageBounds();
if (selectionPageBounds) {
const offset = selectionPageBounds!.center.sub(position);
editor.updateShapes(
editor.getSelectedShapes().map((shape) => {
const localRotation = editor
.getShapeParentTransform(shape)
.decompose().rotation;
const localDelta = Vec.Rot(offset, -localRotation);
return {
id: shape.id,
type: shape.type,
x: shape.x! - localDelta.x,
y: shape.y! - localDelta.y,
};
})
);
}
// Zoom out to fit the shapes, if necessary
selectionPageBounds = editor.getSelectionPageBounds();
if (
selectionPageBounds &&
!viewportPageBounds.contains(selectionPageBounds)
) {
editor.zoomToSelection();
}
}
export function createEmptyBookmarkShape(
editor: Editor,
url: string,
position: VecLike
): TLBookmarkShape {
const partial: TLShapePartial = {
id: createShapeId(),
type: "bookmark",
x: position.x - 150,
y: position.y - 160,
opacity: 1,
props: {
assetId: null,
url,
},
};
editor.batch(() => {
editor.createShapes([partial]).select(partial.id);
centerSelectionAroundPoint(editor, position);
});
return editor.getShape(partial.id) as TLBookmarkShape;
}

View file

@ -1,236 +0,0 @@
// @ts-nocheck TODO: A LOT OF TS ERRORS HERE
import {
AssetRecordType,
Editor,
TLAsset,
TLAssetId,
TLBookmarkShape,
TLExternalContentSource,
TLShapePartial,
Vec,
VecLike,
createShapeId,
getEmbedInfo,
getHashForString,
} from "tldraw";
export default async function createEmbedsFromUrl({
url,
point,
sources,
editor,
}: {
url: string;
point?: VecLike | undefined;
sources?: TLExternalContentSource[] | undefined;
editor: Editor;
}) {
const position =
point ??
(editor.inputs.shiftKey
? editor.inputs.currentPagePoint
: editor.getViewportPageBounds().center);
if (url?.includes("x.com") || url?.includes("twitter.com")) {
return editor.createShape({
type: "Twittercard",
x: position.x - 250,
y: position.y - 150,
props: { url: url },
});
}
// try to paste as an embed first
const embedInfo = getEmbedInfo(url);
if (embedInfo) {
return editor.putExternalContent({
type: "embed",
url: embedInfo.url,
point,
embed: embedInfo.definition,
});
}
const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url));
const shape = createEmptyBookmarkShape(editor, url, position);
// Use an existing asset if we have one, or else else create a new one
let asset = editor.getAsset(assetId) as TLAsset;
let shouldAlsoCreateAsset = false;
if (!asset) {
shouldAlsoCreateAsset = true;
try {
const bookmarkAsset = await editor.getAssetForExternalContent({
type: "url",
url,
});
const fetchWebsite: {
title?: string;
image?: string;
description?: string;
} = await (
await fetch(`/api/unfirlsite?website=${url}`, {
method: "POST",
})
).json();
if (bookmarkAsset) {
if (fetchWebsite.title) bookmarkAsset.props.title = fetchWebsite.title;
if (fetchWebsite.image) bookmarkAsset.props.image = fetchWebsite.image;
if (fetchWebsite.description)
bookmarkAsset.props.description = fetchWebsite.description;
}
if (!bookmarkAsset) throw Error("Could not create an asset");
asset = bookmarkAsset;
} catch (e) {
console.log(e);
return;
}
}
editor.batch(() => {
if (shouldAlsoCreateAsset) {
editor.createAssets([asset]);
}
editor.updateShapes([
{
id: shape.id,
type: shape.type,
props: {
assetId: asset.id,
},
},
]);
});
}
function isURL(str: string) {
try {
new URL(str);
return true;
} catch {
return false;
}
}
function formatTextToRatio(text: string) {
const totalWidth = text.length;
const maxLineWidth = Math.floor(totalWidth / 10);
const words = text.split(" ");
let lines = [];
let currentLine = "";
words.forEach((word) => {
if ((currentLine + word).length <= maxLineWidth) {
currentLine += (currentLine ? " " : "") + word;
} else {
lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) {
lines.push(currentLine);
}
return { height: (lines.length + 1) * 18, width: maxLineWidth * 10 };
}
export function handleExternalDroppedContent({
text,
editor,
}: {
text: string;
editor: Editor;
}) {
const position = editor.inputs.shiftKey
? editor.inputs.currentPagePoint
: editor.getViewportPageBounds().center;
if (isURL(text)) {
createEmbedsFromUrl({ editor, url: text });
} else {
// editor.createShape({
// type: "text",
// x: position.x - 75,
// y: position.y - 75,
// props: {
// text: text,
// size: "s",
// textAlign: "start",
// },
// });
const { height, width } = formatTextToRatio(text);
editor.createShape({
type: "Textcard",
x: position.x - width / 2,
y: position.y - height / 2,
props: {
content: text,
extrainfo: "https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
w: width,
h: height,
},
});
}
}
function centerSelectionAroundPoint(editor: Editor, position: VecLike) {
// Re-position shapes so that the center of the group is at the provided point
const viewportPageBounds = editor.getViewportPageBounds();
let selectionPageBounds = editor.getSelectionPageBounds();
if (selectionPageBounds) {
const offset = selectionPageBounds!.center.sub(position);
editor.updateShapes(
editor.getSelectedShapes().map((shape) => {
const localRotation = editor
.getShapeParentTransform(shape)
.decompose().rotation;
const localDelta = Vec.Rot(offset, -localRotation);
return {
id: shape.id,
type: shape.type,
x: shape.x! - localDelta.x,
y: shape.y! - localDelta.y,
};
}),
);
}
// Zoom out to fit the shapes, if necessary
selectionPageBounds = editor.getSelectionPageBounds();
if (
selectionPageBounds &&
!viewportPageBounds.contains(selectionPageBounds)
) {
editor.zoomToSelection();
}
}
export function createEmptyBookmarkShape(
editor: Editor,
url: string,
position: VecLike,
): TLBookmarkShape {
const partial: TLShapePartial = {
id: createShapeId(),
type: "bookmark",
x: position.x - 150,
y: position.y - 160,
opacity: 1,
props: {
assetId: null,
url,
},
};
editor.batch(() => {
editor.createShapes([partial]).select(partial.id);
centerSelectionAroundPoint(editor, position);
});
return editor.getShape(partial.id) as TLBookmarkShape;
}

View file

@ -1,7 +1,8 @@
import { createTLStore, defaultShapeUtils, loadSnapshot } from "tldraw";
import { getCanvasData } from "../app/actions/fetchers";
import { twitterCardUtil } from "../components/canvas/twitterCard";
import { textCardUtil } from "../components/canvas/textCard";
// import { twitterCardUtil } from "../components/canvas/custom_nodes/twitterCard";
import { twitterCardUtil } from "@/components/canvas/custom_nodes/twittercard";
import { textCardUtil } from "@/components/canvas/custom_nodes/textcard";
export async function loadRemoteSnapshot(id: string) {
const snapshot = await getCanvasData(id);

View file

@ -0,0 +1,41 @@
import cheerio from 'cheerio'
export async function unfurl(url: string) {
const response = await fetch(url)
if (response.status >= 400) {
throw new Error(`Error fetching url: ${response.status}`)
}
const contentType = response.headers.get('content-type')
if (!contentType?.includes('text/html')) {
throw new Error(`Content-type not right: ${contentType}`)
}
const content = await response.text()
const $ = cheerio.load(content)
const og: { [key: string]: string | undefined } = {}
const twitter: { [key: string]: string | undefined } = {}
// @ts-ignore trust
$('meta[property^=og:]').each((_, el) => (og[$(el).attr('property')!] = $(el).attr('content')))
// @ts-ignore trust
$('meta[name^=twitter:]').each((_, el) => (twitter[$(el).attr('name')!] = $(el).attr('content')))
const title = og['og:title'] ?? twitter['twitter:title'] ?? $('title').text() ?? undefined
const description =
og['og:description'] ??
twitter['twitter:description'] ??
$('meta[name="description"]').attr('content') ??
undefined
const image = og['og:image:secure_url'] ?? og['og:image'] ?? twitter['twitter:image'] ?? undefined
const favicon =
$('link[rel="apple-touch-icon"]').attr('href') ??
$('link[rel="icon"]').attr('href') ??
undefined
return {
title,
description,
image,
favicon,
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 846 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M18 5C18 4.44772 17.5523 4 17 4H13C12.4477 4 12 4.44772 12 5L12 28H3C2.44772 28 2 28.4477 2 29C2 29.5523 2.44772 30 3 30L27 30C27.5523 30 28 29.5523 28 29C28 28.4477 27.5523 28 27 28H18L18 5Z"/></svg>

Before

Width:  |  Height:  |  Size: 316 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M1.99976 13C1.99976 12.4477 2.44747 12 2.99976 12H26.9998C27.552 12 27.9998 12.4477 27.9998 13V17C27.9998 17.5523 27.552 18 26.9998 18H2.99976C2.44747 18 1.99976 17.5523 1.99976 17V13Z"/><path fill="#000" d="M13.9998 3C13.9998 2.44772 14.4475 2 14.9998 2C15.552 2 15.9998 2.44772 15.9998 3V27C15.9998 27.5523 15.552 28 14.9998 28C14.4475 28 13.9998 27.5523 13.9998 27V3Z"/></svg>

Before

Width:  |  Height:  |  Size: 495 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M12.9998 2C12.4475 2 11.9998 2.44772 11.9998 3V14H2.99976C2.44747 14 1.99976 14.4477 1.99976 15C1.99976 15.5523 2.44747 16 2.99976 16H11.9998V27C11.9998 27.5523 12.4475 28 12.9998 28H16.9998C17.552 28 17.9998 27.5523 17.9998 27V16H26.9998C27.552 16 27.9998 15.5523 27.9998 15C27.9998 14.4477 27.552 14 26.9998 14H17.9998V3C17.9998 2.44772 17.552 2 16.9998 2H12.9998Z"/></svg>

Before

Width:  |  Height:  |  Size: 491 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M0 3C0 2.44772 0.447715 2 1 2C1.55228 2 2 2.44772 2 3V27C2 27.5523 1.55228 28 1 28C0.447715 28 0 27.5523 0 27V3Z"/><path fill="#000" d="M0 12H25C25.5523 12 26 12.4477 26 13V17C26 17.5523 25.5523 18 25 18H0V12Z"/></svg>

Before

Width:  |  Height:  |  Size: 334 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M3.99976 13C3.99976 12.4477 4.44747 12 4.99976 12H29.9998V18H4.99976C4.44747 18 3.99976 17.5523 3.99976 17V13Z"/><path fill="#000" d="M27.9998 3C27.9998 2.44772 28.4475 2 28.9998 2C29.552 2 29.9998 2.44772 29.9998 3V27C29.9998 27.5523 29.552 28 28.9998 28C28.4475 28 27.9998 27.5523 27.9998 27V3Z"/></svg>

Before

Width:  |  Height:  |  Size: 421 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M2.99988 1.52588e-05C2.44759 1.52588e-05 1.99988 0.447731 1.99988 1.00002C1.99988 1.5523 2.44759 2.00002 2.99988 2.00002H11.9999V25C11.9999 25.5523 12.4476 26 12.9999 26H16.9999C17.5522 26 17.9999 25.5523 17.9999 25V2.00002H26.9999C27.5522 2.00002 27.9999 1.5523 27.9999 1.00002C27.9999 0.447731 27.5522 1.52588e-05 26.9999 1.52588e-05H2.99988Z"/></svg>

Before

Width:  |  Height:  |  Size: 469 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12.4996 21.5001L5.99963 15.0001M5.99963 15.0001L12.4996 8.50012M5.99963 15.0001L23.9999 15.0001"/></svg>

Before

Width:  |  Height:  |  Size: 287 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 15L27 15M13 26L29 15L13 4"/></svg>

Before

Width:  |  Height:  |  Size: 219 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-width="2" d="M1 15L29 15M29 15V2M29 15V28"/></svg>

Before

Width:  |  Height:  |  Size: 195 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-width="2" d="M18.4142 3.82822L28.3137 13.7277C29.0948 14.5088 29.0948 15.7751 28.3137 16.5561L18.4142 26.4556C17.6332 27.2367 16.3669 27.2367 15.5858 26.4556L5.68631 16.5561C4.90526 15.7751 4.90526 14.5088 5.68631 13.7277L15.5858 3.82822C16.3669 3.04717 17.6332 3.04717 18.4142 3.82822Z"/><path stroke="#000" stroke-linecap="round" stroke-width="2" d="M1 15H5"/></svg>

Before

Width:  |  Height:  |  Size: 490 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-width="2" d="M29 15C29 21.0751 24.0751 26 18 26C11.9249 26 7 21.0751 7 15C7 8.92487 11.9249 4 18 4C24.0751 4 29 8.92487 29 15Z"/><path stroke="#000" stroke-linecap="round" stroke-width="2" d="M1 15H6"/></svg>

Before

Width:  |  Height:  |  Size: 330 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-width="2" d="M1 15L29 15"/></svg>

Before

Width:  |  Height:  |  Size: 178 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-width="2" d="M1 15L5 15M8 27H26C27.6569 27 29 25.6569 29 24V6C29 4.34315 27.6569 3 26 3H8C6.34315 3 5 4.34315 5 6V24C5 25.6569 6.34315 27 8 27Z"/></svg>

Before

Width:  |  Height:  |  Size: 297 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M1 14C0.447715 14 0 14.4477 0 15C5.96046e-08 15.5523 0.447715 16 1 16L1 14ZM29 2H30C30 1.62447 29.7896 1.2806 29.4553 1.10964C29.1209 0.938677 28.7189 0.969452 28.4145 1.18932L29 2ZM29 28L28.4145 28.8107C28.7189 29.0305 29.1209 29.0613 29.4553 28.8904C29.7896 28.7194 30 28.3755 30 28H29ZM1 16L11 16V14L1 14L1 16ZM28 2V28H30V2H28ZM29.5855 27.1893L11.5855 14.1893L10.4145 15.8107L28.4145 28.8107L29.5855 27.1893ZM11.5855 15.8107L29.5855 2.81068L28.4145 1.18932L10.4145 14.1893L11.5855 15.8107Z"/></svg>

Before

Width:  |  Height:  |  Size: 617 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 15.1539L11.6923 15.1539M12.6923 3.46155V26.5385L28.8462 15L12.6923 3.46155Z"/></svg>

Before

Width:  |  Height:  |  Size: 269 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M1.98125 12.4723C1.83836 12.6938 2.09551 13.0008 2.33213 12.8846C3.14685 12.4842 3.93568 12.0619 4.70733 11.5967C4.93448 11.4598 5.18303 11.7046 5.0359 11.9253C3.13151 14.7818 -1.59634 21.7981 3.60918 22.2578C6.76899 22.5368 9.93894 19.9616 12.9133 17.2735C13.1318 17.0759 13.4376 17.335 13.2743 17.5802C10.8537 21.2143 6.41515 28.636 12.2493 28.99C17.3234 29.298 16.1533 24.5977 27.679 18.2524C28.5169 17.7911 28.7805 16.8396 28.5707 15.9125C28.1988 13.9184 24.4364 13.9496 22.3466 14.4984C22.0925 14.5651 21.8477 14.245 22.0182 14.045C24.5643 11.0592 31.8711 3.60414 27.2697 1.27741C23.219 -0.770836 13.918 8.89819 9.96535 12.5406C9.75706 12.7325 9.52989 12.5192 9.69793 12.2912C11.6423 9.65308 14.1864 6.73192 14.6429 3.69364C14.7073 3.26555 14.628 2.86103 14.4053 2.48008C14.1825 2.09913 13.8587 1.8145 13.4339 1.6262C9.1247 -0.284007 4.20268 9.02761 1.98125 12.4723Z"/></svg>

Before

Width:  |  Height:  |  Size: 996 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.50024 12.6388L15.0002 6.5M15.0002 6.5L21.5002 12.6388M15.0002 6.5L15.0002 23.5"/></svg>

Before

Width:  |  Height:  |  Size: 272 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.50024 15.4998L15.0002 8.99976M15.0002 8.99976L21.5002 15.4998M15.0002 8.99976L15.0002 27M3 3L27 3"/></svg>

Before

Width:  |  Height:  |  Size: 291 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M28.0001 12C28.0001 11.4477 27.5524 11 27.0001 11C26.4478 11 26.0001 11.4477 26.0001 12V26H12C11.4477 26 11 26.4477 11 27C11 27.5523 11.4477 28 12 28H26.9C27.4523 28 28.0001 27.4522 28.0001 26.8999V12Z"/><path fill="#000" d="M2 18C2 18.5523 2.44769 19 2.99997 19C3.55225 19 4 18.5523 4 18L4 4L18 4C18.5523 4 19 3.55228 19 3C19 2.44772 18.5523 2 18 2L3.10007 2C2.54779 2 2 2.54781 2 3.10009L2 18Z"/><rect width="35.946" height="2" x="1.584" y="27.002" fill="#000" rx="1" transform="rotate(-45 1.58411 27.0018)"/></svg>

Before

Width:  |  Height:  |  Size: 633 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-width="2" d="M14.9998 27.2949C21.7904 27.2949 27.2954 21.7899 27.2954 14.9993C27.2954 8.20858 21.7904 2.70361 14.9998 2.70361C8.20907 2.70361 2.7041 8.20858 2.7041 14.9993C2.7041 21.7899 8.20907 27.2949 14.9998 27.2949Z"/><path fill="#000" d="M20.7075 9.39489C21.1367 9.67551 21.2572 10.2509 20.9765 10.6801L14.6629 20.3363C14.5141 20.5639 14.273 20.7148 14.0033 20.7492C13.7336 20.7836 13.4624 20.6981 13.2612 20.5152L9.17587 16.8013C8.79644 16.4564 8.76848 15.8691 9.11342 15.4897C9.45835 15.1103 10.0456 15.0823 10.425 15.4273L13.7046 18.4087L19.4223 9.66389C19.7029 9.2347 20.2784 9.11426 20.7075 9.39489Z"/></svg>

Before

Width:  |  Height:  |  Size: 740 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M22.9339 7.45369C23.5118 7.83148 23.6739 8.60615 23.2961 9.18396L14.7961 22.184C14.5958 22.4903 14.2712 22.6935 13.9082 22.7398C13.5451 22.7862 13.1799 22.6711 12.909 22.4248L7.40905 17.4248C6.89822 16.9604 6.86058 16.1699 7.32496 15.6591C7.78935 15.1482 8.57991 15.1106 9.09073 15.575L13.506 19.5888L21.2037 7.81584C21.5815 7.23803 22.3561 7.07589 22.9339 7.45369Z"/></svg>

Before

Width:  |  Height:  |  Size: 490 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M6.27047 12.3161C6.6482 11.9131 7.28103 11.8927 7.68394 12.2705L15 19.1293L22.3161 12.2705C22.719 11.8927 23.3518 11.9131 23.7295 12.3161C24.1073 12.719 24.0869 13.3518 23.6839 13.7295L15.6839 21.2295C15.2993 21.5902 14.7007 21.5902 14.3161 21.2295L6.31606 13.7295C5.91315 13.3518 5.89274 12.719 6.27047 12.3161Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 477 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M17.6836 6.27028C18.0865 6.64801 18.1069 7.28085 17.7292 7.68376L10.8704 14.9998L17.7292 22.3159C18.1069 22.7188 18.0865 23.3516 17.6836 23.7294C17.2807 24.1071 16.6478 24.0867 16.2701 23.6838L8.7701 15.6838C8.40948 15.2991 8.40948 14.7005 8.7701 14.3159L16.2701 6.31588C16.6478 5.91297 17.2807 5.89255 17.6836 6.27028Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 484 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M12.3168 6.27013C12.7197 5.8924 13.3525 5.91281 13.7303 6.31573L21.2303 14.3157C21.5909 14.7004 21.5909 15.299 21.2303 15.6836L13.7303 23.6836C13.3525 24.0865 12.7197 24.1069 12.3168 23.7292C11.9139 23.3515 11.8935 22.7186 12.2712 22.3157L19.13 14.9997L12.2712 7.68361C11.8935 7.2807 11.9139 6.64786 12.3168 6.27013Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 481 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M6.27047 17.6839C6.6482 18.0869 7.28103 18.1073 7.68394 17.7295L15 10.8707L22.3161 17.7295C22.719 18.1073 23.3518 18.0869 23.7295 17.6839C24.1073 17.281 24.0869 16.6482 23.6839 16.2705L15.6839 8.77046C15.2993 8.40984 14.7007 8.40984 14.3161 8.77046L6.31606 16.2705C5.91315 16.6482 5.89274 17.281 6.27047 17.6839Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 477 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7.5271 8.73255L13.7947 15.0002L7.52722 21.2677M16.2052 8.73242L22.4728 15L16.2053 21.2675"/></svg>

Before

Width:  |  Height:  |  Size: 281 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M22.4728 21.2675L16.2052 14.9999L22.4727 8.73242M13.7947 21.2676L7.5271 15L13.7946 8.73254"/></svg>

Before

Width:  |  Height:  |  Size: 281 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M8 2V4H18V2H8ZM6 1.5C6 0.671573 6.67157 0 7.5 0H18.5C19.3284 0 20 0.671572 20 1.5V2H21C22.6569 2 24 3.34315 24 5V14H22V5C22 4.44772 21.5523 4 21 4H20V4.5C20 5.32843 19.3284 6 18.5 6H7.5C6.67157 6 6 5.32843 6 4.5V4H5C4.44771 4 4 4.44772 4 5V25C4 25.5523 4.44772 26 5 26H12V28H5C3.34315 28 2 26.6569 2 25V5C2 3.34314 3.34315 2 5 2H6V1.5Z" clip-rule="evenodd"/><path fill="#000" d="M27.5197 17.173C28.0099 17.4936 28.1475 18.1509 27.827 18.6411L20.6149 29.6713C20.445 29.9313 20.1696 30.1037 19.8615 30.143C19.5534 30.1823 19.2436 30.0846 19.0138 29.8757L14.3472 25.6333C13.9137 25.2393 13.8818 24.5685 14.2758 24.1351C14.6698 23.7017 15.3406 23.6697 15.774 24.0638L19.5203 27.4694L26.0516 17.4803C26.3721 16.9901 27.0294 16.8525 27.5197 17.173Z"/></svg>

Before

Width:  |  Height:  |  Size: 887 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M8 2V4H18V2H8ZM6 1.5C6 0.671573 6.67157 0 7.5 0H18.5C19.3284 0 20 0.671572 20 1.5V2H21C22.6569 2 24 3.34315 24 5V14H22V5C22 4.44772 21.5523 4 21 4H20V4.5C20 5.32843 19.3284 6 18.5 6H7.5C6.67157 6 6 5.32843 6 4.5V4H5C4.44771 4 4 4.44772 4 5V25C4 25.5523 4.44772 26 5 26H12V28H5C3.34315 28 2 26.6569 2 25V5C2 3.34314 3.34315 2 5 2H6V1.5Z" clip-rule="evenodd"/><path fill="#000" d="M28 29C28 29.5523 27.5523 30 27 30C26.4477 30 26 29.5523 26 29C26 28.4477 26.4477 28 27 28C27.5523 28 28 28.4477 28 29Z"/><path fill="#000" d="M28 25C28 25.5523 27.5523 26 27 26C26.4477 26 26 25.5523 26 25C26 24.4477 26.4477 24 27 24C27.5523 24 28 24.4477 28 25Z"/><path fill="#000" d="M28 21C28 21.5523 27.5523 22 27 22C26.4477 22 26 21.5523 26 21C26 20.4477 26.4477 20 27 20C27.5523 20 28 20.4477 28 21Z"/><path fill="#000" d="M28 17C28 17.5523 27.5523 18 27 18C26.4477 18 26 17.5523 26 17C26 16.4477 26.4477 16 27 16C27.5523 16 28 16.4477 28 17Z"/><path fill="#000" d="M24 17C24 17.5523 23.5523 18 23 18C22.4477 18 22 17.5523 22 17C22 16.4477 22.4477 16 23 16C23.5523 16 24 16.4477 24 17Z"/><path fill="#000" d="M20 17C20 17.5523 19.5523 18 19 18C18.4477 18 18 17.5523 18 17C18 16.4477 18.4477 16 19 16C19.5523 16 20 16.4477 20 17Z"/><path fill="#000" d="M16 17C16 17.5523 15.5523 18 15 18C14.4477 18 14 17.5523 14 17C14 16.4477 14.4477 16 15 16C15.5523 16 16 16.4477 16 17Z"/><path fill="#000" d="M16 21C16 21.5523 15.5523 22 15 22C14.4477 22 14 21.5523 14 21C14 20.4477 14.4477 20 15 20C15.5523 20 16 20.4477 16 21Z"/><path fill="#000" d="M16 25C16 25.5523 15.5523 26 15 26C14.4477 26 14 25.5523 14 25C14 24.4477 14.4477 24 15 24C15.5523 24 16 24.4477 16 25Z"/><path fill="#000" d="M16 29C16 29.5523 15.5523 30 15 30C14.4477 30 14 29.5523 14 29C14 28.4477 14.4477 28 15 28C15.5523 28 16 28.4477 16 29Z"/><path fill="#000" d="M20 29C20 29.5523 19.5523 30 19 30C18.4477 30 18 29.5523 18 29C18 28.4477 18.4477 28 19 28C19.5523 28 20 28.4477 20 29Z"/><path fill="#000" d="M24 29C24 29.5523 23.5523 30 23 30C22.4477 30 22 29.5523 22 29C22 28.4477 22.4477 28 23 28C23.5523 28 24 28.4477 24 29Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><circle cx="15" cy="15" r="13" fill="#000"/></svg>

Before

Width:  |  Height:  |  Size: 145 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M23.5631 8.06315C24.0123 7.61405 24.0123 6.88591 23.5631 6.4368C23.114 5.9877 22.3859 5.9877 21.9368 6.4368L15 13.3736L8.06315 6.4368C7.61405 5.9877 6.88591 5.9877 6.4368 6.4368C5.9877 6.88591 5.9877 7.61405 6.4368 8.06315L13.3736 15L6.4368 21.9368C5.9877 22.3859 5.9877 23.114 6.4368 23.5631C6.88591 24.0123 7.61405 24.0123 8.06315 23.5631L15 16.6263L21.9368 23.5631C22.3859 24.0123 23.114 24.0123 23.5631 23.5631C24.0123 23.114 24.0123 22.3859 23.5631 21.9368L16.6263 15L23.5631 8.06315Z"/></svg>

Before

Width:  |  Height:  |  Size: 614 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M19.4422 11.8925C19.8106 11.524 19.8106 10.9264 19.4422 10.5579C19.0736 10.1894 18.4761 10.1894 18.1076 10.5579L15 13.6654L11.8925 10.5579C11.524 10.1894 10.9264 10.1894 10.5579 10.5579C10.1894 10.9264 10.1894 11.524 10.5579 11.8925L13.6654 15L10.5579 18.1075C10.1894 18.4761 10.1894 19.0736 10.5579 19.4421C10.9264 19.8107 11.524 19.8107 11.8925 19.4421L15 16.3346L18.1076 19.4421C18.4761 19.8107 19.0736 19.8107 19.4422 19.4421C19.8106 19.0736 19.8106 18.4761 19.4422 18.1075L16.3346 15L19.4422 11.8925Z"/><path stroke="#000" stroke-width="2" d="M14.9998 27.2949C21.7904 27.2949 27.2954 21.7899 27.2954 14.9993C27.2954 8.20858 21.7904 2.70361 14.9998 2.70361C8.20907 2.70361 2.7041 8.20858 2.7041 14.9993C2.7041 21.7899 8.20907 27.2949 14.9998 27.2949Z"/></svg>

Before

Width:  |  Height:  |  Size: 879 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M12.4647 2.24741C13.2859 2.08493 14.1339 2 15 2C15.8661 2 16.7141 2.08493 17.5353 2.24741C18.4079 2.42002 18.9753 3.26726 18.8026 4.13978C18.63 5.01229 17.7828 5.57968 16.9103 5.40707C16.2937 5.2851 15.6552 5.2209 15 5.2209C14.3448 5.2209 13.7063 5.2851 13.0897 5.40707C12.2172 5.57968 11.37 5.01229 11.1974 4.13978C11.0248 3.26726 11.5921 2.42002 12.4647 2.24741ZM10.01 4.63292C10.5048 5.372 10.3068 6.37227 9.56771 6.86708C8.50059 7.5815 7.5815 8.50059 6.86708 9.56771C6.37227 10.3068 5.372 10.5048 4.63292 10.01C3.89383 9.51521 3.69581 8.51495 4.19061 7.77586C5.13925 6.35889 6.35889 5.13925 7.77586 4.19061C8.51495 3.69581 9.51521 3.89383 10.01 4.63292ZM19.99 4.63292C20.4848 3.89383 21.4851 3.69581 22.2241 4.19061C23.6411 5.13925 24.8607 6.35889 25.8094 7.77586C26.3042 8.51495 26.1062 9.51521 25.3671 10.01C24.628 10.5048 23.6277 10.3068 23.1329 9.56771C22.4185 8.50059 21.4994 7.5815 20.4323 6.86708C19.6932 6.37227 19.4952 5.372 19.99 4.63292ZM4.13978 11.1974C5.01229 11.37 5.57968 12.2172 5.40707 13.0897C5.2851 13.7063 5.2209 14.3448 5.2209 15C5.2209 15.6552 5.2851 16.2937 5.40707 16.9103C5.57968 17.7828 5.01229 18.63 4.13978 18.8026C3.26726 18.9752 2.42002 18.4079 2.24741 17.5353C2.08493 16.7141 2 15.8661 2 15C2 14.1339 2.08493 13.2859 2.24741 12.4647C2.42002 11.5921 3.26726 11.0247 4.13978 11.1974ZM25.8602 11.1974C26.7327 11.0248 27.58 11.5921 27.7526 12.4647C27.9151 13.2859 28 14.1339 28 15C28 15.8661 27.9151 16.7141 27.7526 17.5353C27.58 18.4079 26.7327 18.9753 25.8602 18.8026C24.9877 18.63 24.4203 17.7828 24.5929 16.9103C24.7149 16.2937 24.7791 15.6552 24.7791 15C24.7791 14.3448 24.7149 13.7063 24.5929 13.0897C24.4203 12.2172 24.9877 11.37 25.8602 11.1974ZM4.63292 19.99C5.372 19.4952 6.37227 19.6932 6.86708 20.4323C7.5815 21.4994 8.50059 22.4185 9.56771 23.1329C10.3068 23.6277 10.5048 24.628 10.01 25.3671C9.51521 26.1062 8.51495 26.3042 7.77586 25.8094C6.35889 24.8607 5.13925 23.6411 4.19061 22.2241C3.69581 21.4851 3.89383 20.4848 4.63292 19.99ZM25.3671 19.99C26.1062 20.4848 26.3042 21.4851 25.8094 22.2241C24.8607 23.6411 23.6411 24.8607 22.2241 25.8094C21.4851 26.3042 20.4848 26.1062 19.99 25.3671C19.4952 24.628 19.6932 23.6277 20.4323 23.1329C21.4994 22.4185 22.4185 21.4994 23.1329 20.4323C23.6277 19.6932 24.628 19.4952 25.3671 19.99ZM11.1974 25.8602C11.37 24.9877 12.2172 24.4203 13.0897 24.5929C13.7063 24.7149 14.3448 24.7791 15 24.7791C15.6552 24.7791 16.2937 24.7149 16.9103 24.5929C17.7828 24.4203 18.63 24.9877 18.8026 25.8602C18.9752 26.7327 18.4079 27.58 17.5353 27.7526C16.7141 27.9151 15.8661 28 15 28C14.1339 28 13.2859 27.9151 12.4647 27.7526C11.5921 27.58 11.0247 26.7327 11.1974 25.8602Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M17 3.7915C17 4.89607 16.1046 5.7915 15 5.7915C13.8954 5.7915 13 4.89607 13 3.7915C13 2.68693 13.8954 1.7915 15 1.7915C16.1046 1.7915 17 2.68693 17 3.7915Z"/><path fill="#000" d="M17 25.7915C17 26.8961 16.1046 27.7915 15 27.7915C13.8954 27.7915 13 26.8961 13 25.7915C13 24.6869 13.8954 23.7915 15 23.7915C16.1046 23.7915 17 24.6869 17 25.7915Z"/><path fill="#000" d="M22.232 6.26525C21.6798 7.22184 20.4566 7.54959 19.5 6.9973C18.5434 6.44502 18.2157 5.22184 18.7679 4.26525C19.3202 3.30867 20.5434 2.98092 21.5 3.5332C22.4566 4.08549 22.7843 5.30867 22.232 6.26525Z"/><path fill="#000" d="M11.232 25.3178C10.6798 26.2744 9.45658 26.6021 8.5 26.0499C7.54341 25.4976 7.21566 24.2744 7.76794 23.3178C8.32023 22.3612 9.54341 22.0335 10.5 22.5858C11.4566 23.138 11.7843 24.3612 11.232 25.3178Z"/><path fill="#000" d="M25.5262 11.0236C24.5697 11.5758 23.3465 11.2481 22.7942 10.2915C22.2419 9.33491 22.5697 8.11173 23.5263 7.55945C24.4828 7.00716 25.706 7.33491 26.2583 8.2915C26.8106 9.24808 26.4828 10.4713 25.5262 11.0236Z"/><path fill="#000" d="M6.47376 22.0236C5.51717 22.5758 4.29399 22.2481 3.74171 21.2915C3.18942 20.3349 3.51717 19.1117 4.47376 18.5594C5.43035 18.0072 6.65353 18.3349 7.20581 19.2915C7.7581 20.2481 7.43035 21.4713 6.47376 22.0236Z"/><path fill="#000" d="M26 16.7915C24.8954 16.7915 24 15.8961 24 14.7915C24 13.6869 24.8954 12.7915 26 12.7915C27.1046 12.7915 28 13.6869 28 14.7915C28 15.8961 27.1046 16.7915 26 16.7915Z"/><path fill="#000" d="M4 16.7915C2.89543 16.7915 2 15.8961 2 14.7915C2 13.6869 2.89543 12.7915 4 12.7915C5.10457 12.7915 6 13.6869 6 14.7915C6 15.8961 5.10457 16.7915 4 16.7915Z"/><path fill="#000" d="M23.5262 22.0236C22.5697 21.4713 22.2419 20.2481 22.7942 19.2915C23.3465 18.3349 24.5697 18.0072 25.5263 18.5594C26.4828 19.1117 26.8106 20.3349 26.2583 21.2915C25.706 22.2481 24.4828 22.5758 23.5262 22.0236Z"/><path fill="#000" d="M4.47376 11.0236C3.51717 10.4713 3.18942 9.24808 3.74171 8.2915C4.29399 7.33491 5.51717 7.00716 6.47376 7.55945C7.43035 8.11173 7.7581 9.33491 7.20581 10.2915C6.65353 11.2481 5.43034 11.5758 4.47376 11.0236Z"/><path fill="#000" d="M18.768 25.3178C18.2157 24.3612 18.5434 23.138 19.5 22.5857C20.4566 22.0334 21.6798 22.3612 22.2321 23.3178C22.7843 24.2743 22.4566 25.4975 21.5 26.0498C20.5434 26.6021 19.3202 26.2743 18.768 25.3178Z"/><path fill="#000" d="M7.76795 6.2652C7.21567 5.30862 7.54342 4.08544 8.5 3.53315C9.45659 2.98087 10.6798 3.30862 11.2321 4.2652C11.7843 5.22179 11.4566 6.44497 10.5 6.99725C9.54342 7.54954 8.32024 7.22179 7.76795 6.2652Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M21.888 15.7331C21.888 11.5738 17.8658 8.92277 13.1123 8.92277C9.48121 8.92277 6.98189 11.5642 6.27145 14.1692C4.96079 18.9749 7.8945 23.0277 11.6746 24.0908C17.7126 25.2814 21.888 21.4921 21.888 15.7331ZM17.1431 2.1844C22.0563 2.9037 26.0951 6.6011 27.4241 11.3531C31.2814 24.4598 14.8135 33.1322 6.27145 24.5902C-3.45849 14.8611 4.83063 0.187141 17.1431 2.1844Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 528 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><circle cx="15" cy="15" r="11.5" stroke="#000" stroke-width="3"/></svg>

Before

Width:  |  Height:  |  Size: 166 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" fill-rule="evenodd" d="M14.6246 8.08966C8.94285 8.1985 3.90974 10.7717 0.702407 14.7107C0.294387 15.2118 0.414914 15.9436 0.934298 16.3281C1.47615 16.7292 2.23941 16.5967 2.66586 16.0746C5.14686 13.0369 8.88675 10.9431 13.1591 10.4712L14.6246 8.08966ZM10.7967 14.3104C8.56295 15.0878 6.64328 16.4668 5.26387 18.2363C4.90293 18.6993 5.02803 19.3584 5.4999 19.7077C6.03125 20.101 6.78272 19.9442 7.19471 19.4272C7.56521 18.9623 7.98227 18.5316 8.43945 18.1411L10.7967 14.3104ZM20.3687 17.2713L21.5219 15.3972C22.7718 16.1511 23.8624 17.1153 24.7362 18.2362C25.0972 18.6992 24.9721 19.3583 24.5002 19.7076C23.9689 20.1009 23.2174 19.9442 22.8054 19.4272C22.1335 18.584 21.3085 17.8534 20.3687 17.2713ZM17.4759 21.9723L18.5682 20.1972C19.2365 20.6457 19.8039 21.2169 20.2326 21.8754C20.4882 22.268 20.3516 22.7786 19.975 23.0573C19.4174 23.4701 18.5978 23.1841 18.1652 22.6416C17.9665 22.3924 17.7347 22.1674 17.4759 21.9723ZM23.2506 12.5879L24.4705 10.6054C26.3359 11.6891 27.9726 13.0833 29.2977 14.7106C29.7057 15.2117 29.5852 15.9436 29.0658 16.3281C28.5239 16.7291 27.7607 16.5967 27.3342 16.0746C26.2093 14.6972 24.8255 13.5139 23.2506 12.5879Z" clip-rule="evenodd"/><rect width="3" height="33.372" x="23.376" y=".169" fill="#000" rx="1.5" transform="rotate(31.6059 23.376 0.169067)"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><g clip-path="url(#clip0_916_107895)"><path fill="#000" d="M8.16097 14.0244C8.64823 13.4997 9.30953 13.2032 10.0001 13.2C10.6908 13.2032 11.3521 13.4997 11.8393 14.0244C12.3266 14.5492 12.6001 15.2595 12.6001 16C12.6001 16.7404 12.3266 17.4507 11.8393 17.9755C11.3521 18.5002 10.6908 18.7967 10.0001 18.8C9.30953 18.7967 8.64823 18.5002 8.16097 17.9755C7.67371 17.4507 7.40015 16.7404 7.40015 16C7.40015 15.2595 7.67371 14.5492 8.16097 14.0244Z"/><path fill="#000" d="M18.1585 14.0244C18.6458 13.4997 19.3071 13.2032 19.9977 13.2C20.6883 13.2032 21.3496 13.4997 21.8369 14.0244C22.3241 14.5492 22.5977 15.2595 22.5977 16C22.5977 16.7404 22.3241 17.4507 21.8369 17.9755C21.3496 18.5002 20.6883 18.7967 19.9977 18.8C19.3071 18.7967 18.6458 18.5002 18.1585 17.9755C17.6713 17.4507 17.3977 16.7404 17.3977 16C17.3977 15.2595 17.6713 14.5492 18.1585 14.0244Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.79999C11.7912 6.08523 10.7517 4.71816 10 4.60004C8.47088 4.76658 6.03082 5.624 4.6 6.40004C3.57476 7.57227 2.52044 10.5558 2.10918 12.2C1.425 14.9355 1.04398 18.2476 0.994873 21.4C1.95111 23.2185 5.42945 25.2 7.5 25.4C7.98332 24.876 9.4 22.6 9.4 22.6M17.9951 6.79999C18.2039 6.08523 19.2434 4.71816 19.9951 4.60004C21.5242 4.76658 23.9643 5.624 25.3951 6.40004C26.4204 7.57227 27.4747 10.5558 27.8859 12.2C28.5701 14.9355 28.9511 18.2476 29.0002 21.4C28.044 23.2185 24.5657 25.2 22.4951 25.4C22.0118 24.876 20.5951 22.6 20.5951 22.6M7 9C7.6 8.2 9.99998 7 15 7C20 7 22.4 8.2 23 9M5.80005 20.4002C6.6 21.6002 8.40002 23.0002 15 23.0002C21.6001 23.0002 23.4 21.6003 24.2001 20.4002"/></g><defs><clipPath id="clip0_916_107895"><rect width="30" height="30" fill="#fff"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M1.99976 12H10.9998C11.552 12 11.9998 12.4477 11.9998 13V17C11.9998 17.5523 11.552 18 10.9998 18H1.99976V12Z"/><path fill="#000" d="M17.9998 13C17.9998 12.4477 18.4475 12 18.9998 12H27.9998V18H18.9998C18.4475 18 17.9998 17.5523 17.9998 17V13Z"/><path fill="#000" d="M0 3C0 2.44772 0.447715 2 1 2C1.55228 2 2 2.44772 2 3V27C2 27.5523 1.55228 28 1 28C0.447715 28 0 27.5523 0 27V3Z"/><path fill="#000" d="M27.9998 3C27.9998 2.44772 28.4475 2 28.9998 2C29.552 2 29.9998 2.44772 29.9998 3V27C29.9998 27.5523 29.552 28 28.9998 28C28.4475 28 27.9998 27.5523 27.9998 27V3Z"/></svg>

Before

Width:  |  Height:  |  Size: 689 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M12.0002 2L18.0002 2V11C18.0002 11.5523 17.5525 12 17.0002 12H13.0002C12.448 12 12.0002 11.5523 12.0002 11V2Z"/><path fill="#000" d="M12.0002 19C12.0002 18.4477 12.448 18 13.0002 18H17.0002C17.5525 18 18.0002 18.4477 18.0002 19V28H12.0002V19Z"/><path fill="#000" d="M2 1C2 0.447715 2.44772 0 3 0H27C27.5523 0 28 0.447715 28 1C28 1.55228 27.5523 2 27 2H3C2.44772 2 2 1.55228 2 1Z"/><path fill="#000" d="M2 29C2 28.4477 2.44772 28 3 28H27C27.5523 28 28 28.4477 28 29C28 29.5523 27.5523 30 27 30H3C2.44772 30 2 29.5523 2 29Z"/></svg>

Before

Width:  |  Height:  |  Size: 646 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><circle cx="14.5" cy="15.5" r="4.5" fill="#000"/></svg>

Before

Width:  |  Height:  |  Size: 150 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M7.25 15C7.25 16.2426 6.24264 17.25 5 17.25C3.75736 17.25 2.75 16.2426 2.75 15C2.75 13.7574 3.75736 12.75 5 12.75C6.24264 12.75 7.25 13.7574 7.25 15Z"/><path fill="#000" d="M17.25 15C17.25 16.2426 16.2426 17.25 15 17.25C13.7574 17.25 12.75 16.2426 12.75 15C12.75 13.7574 13.7574 12.75 15 12.75C16.2426 12.75 17.25 13.7574 17.25 15Z"/><path fill="#000" d="M27.25 15C27.25 16.2426 26.2426 17.25 25 17.25C23.7574 17.25 22.75 16.2426 22.75 15C22.75 13.7574 23.7574 12.75 25 12.75C26.2426 12.75 27.25 13.7574 27.25 15Z"/></svg>

Before

Width:  |  Height:  |  Size: 638 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M17.25 5C17.25 6.24264 16.2426 7.25 15 7.25C13.7574 7.25 12.75 6.24264 12.75 5C12.75 3.75736 13.7574 2.75 15 2.75C16.2426 2.75 17.25 3.75736 17.25 5Z"/><path fill="#000" d="M17.25 15C17.25 16.2426 16.2426 17.25 15 17.25C13.7574 17.25 12.75 16.2426 12.75 15C12.75 13.7574 13.7574 12.75 15 12.75C16.2426 12.75 17.25 13.7574 17.25 15Z"/><path fill="#000" d="M17.25 25C17.25 26.2426 16.2426 27.25 15 27.25C13.7574 27.25 12.75 26.2426 12.75 25C12.75 23.7574 13.7574 22.75 15 22.75C16.2426 22.75 17.25 23.7574 17.25 25Z"/></svg>

Before

Width:  |  Height:  |  Size: 638 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none" viewBox="0 0 30 30"><path fill="#000" d="M11 9.25C12.2426 9.25 13.25 8.24264 13.25 7C13.25 5.75736 12.2426 4.75 11 4.75C9.75736 4.75 8.75 5.75736 8.75 7C8.75 8.24264 9.75736 9.25 11 9.25Z"/><path fill="#000" d="M19 9.25C20.2426 9.25 21.25 8.24264 21.25 7C21.25 5.75736 20.2426 4.75 19 4.75C17.7574 4.75 16.75 5.75736 16.75 7C16.75 8.24264 17.7574 9.25 19 9.25Z"/><path fill="#000" d="M21.25 15C21.25 16.2426 20.2426 17.25 19 17.25C17.7574 17.25 16.75 16.2426 16.75 15C16.75 13.7574 17.7574 12.75 19 12.75C20.2426 12.75 21.25 13.7574 21.25 15Z"/><path fill="#000" d="M11 17.25C12.2426 17.25 13.25 16.2426 13.25 15C13.25 13.7574 12.2426 12.75 11 12.75C9.75736 12.75 8.75 13.7574 8.75 15C8.75 16.2426 9.75736 17.25 11 17.25Z"/><path fill="#000" d="M21.25 23C21.25 24.2426 20.2426 25.25 19 25.25C17.7574 25.25 16.75 24.2426 16.75 23C16.75 21.7574 17.7574 20.75 19 20.75C20.2426 20.75 21.25 21.7574 21.25 23Z"/><path fill="#000" d="M11 25.25C12.2426 25.25 13.25 24.2426 13.25 23C13.25 21.7574 12.2426 20.75 11 20.75C9.75736 20.75 8.75 21.7574 8.75 23C8.75 24.2426 9.75736 25.25 11 25.25Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Some files were not shown because too many files have changed in this diff Show more