From 99659df86f3daa44d2d58c90b43587a25e961308 Mon Sep 17 00:00:00 2001 From: codetorso Date: Sun, 9 Jun 2024 20:38:37 +0530 Subject: [PATCH] before color edits --- apps/web/app/(canvas)/canvas.tsx | 53 ++++++++ apps/web/app/(canvas)/canvas/layout.tsx | 13 ++ apps/web/app/(canvas)/canvas/page.tsx | 96 ++++++++++++++ apps/web/app/(canvas)/canvasStyles.css | 24 ++++ apps/web/app/(canvas)/enabledComp.tsx | 22 ++++ apps/web/app/(canvas)/lib/createAssetUrl.ts | 73 +++++++++++ apps/web/app/(canvas)/lib/createEmbeds.ts | 134 ++++++++++++++++++++ apps/web/app/(canvas)/lib/loadSnap.ts | 13 ++ apps/web/app/(canvas)/savesnap.tsx | 43 +++++++ apps/web/app/(canvas)/svg.tsx | 97 ++++++++++++++ apps/web/app/(canvas)/twitterCard.tsx | 84 ++++++++++++ apps/web/dev.vars | 6 + apps/web/package.json | 3 +- apps/web/wrangler.toml | 4 +- package.json | 1 + turbo.json | 2 +- 16 files changed, 664 insertions(+), 4 deletions(-) create mode 100644 apps/web/app/(canvas)/canvas.tsx create mode 100644 apps/web/app/(canvas)/canvas/layout.tsx create mode 100644 apps/web/app/(canvas)/canvas/page.tsx create mode 100644 apps/web/app/(canvas)/canvasStyles.css create mode 100644 apps/web/app/(canvas)/enabledComp.tsx create mode 100644 apps/web/app/(canvas)/lib/createAssetUrl.ts create mode 100644 apps/web/app/(canvas)/lib/createEmbeds.ts create mode 100644 apps/web/app/(canvas)/lib/loadSnap.ts create mode 100644 apps/web/app/(canvas)/savesnap.tsx create mode 100644 apps/web/app/(canvas)/svg.tsx create mode 100644 apps/web/app/(canvas)/twitterCard.tsx create mode 100644 apps/web/dev.vars diff --git a/apps/web/app/(canvas)/canvas.tsx b/apps/web/app/(canvas)/canvas.tsx new file mode 100644 index 00000000..b836cfe1 --- /dev/null +++ b/apps/web/app/(canvas)/canvas.tsx @@ -0,0 +1,53 @@ +import { useCallback, useEffect, useMemo, 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 createEmbedsFromUrl from "./lib/createEmbeds"; +import { loadRemoteSnapshot } from "./lib/loadSnap"; +import { SaveStatus } from "./savesnap"; + + + +export default function Canvas() { + const [storeWithStatus, setStoreWithStatus] = useState({ + status: "loading", + }); + useEffect(() => { + const fetchStore = async () => { + const store = await loadRemoteSnapshot(); + + 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", isDarkMode: true }); + return ( + +
+ +
+
+ ); +} diff --git a/apps/web/app/(canvas)/canvas/layout.tsx b/apps/web/app/(canvas)/canvas/layout.tsx new file mode 100644 index 00000000..494b52b9 --- /dev/null +++ b/apps/web/app/(canvas)/canvas/layout.tsx @@ -0,0 +1,13 @@ +import "../canvasStyles.css" + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
{children}
+
+ ); +} diff --git a/apps/web/app/(canvas)/canvas/page.tsx b/apps/web/app/(canvas)/canvas/page.tsx new file mode 100644 index 00000000..b1d3adb5 --- /dev/null +++ b/apps/web/app/(canvas)/canvas/page.tsx @@ -0,0 +1,96 @@ +"use client"; + +// import Canvas from "./_components/canvas"; +import Canvas from "../canvas"; +import React, { useState } from "react"; +// import ReactTextareaAutosize from "react-textarea-autosize"; +import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; +import { DragSvg, + SettingsSvg, + LinkSvg, + ThreeDBlock, + TextLoadingSvg, } from "../svg"; + +function page() { + const [value, setValue] = useState(""); + + return ( +
+
+ + +
+
+ Change Filters + +
+
+ { + setValue(e.target.value); + }} + value={value} + // rows={1} + className="w-full resize-none rounded-xl bg-[#171B1F] px-3 py-4 text-xl text-[#989EA4] outline-none focus:outline-none sm:max-h-52" + /> +
+
+
+ +

+ Nvidia will most likely create monopoly in software industry + as they are already largest player in GPU hardware by 20... +

+
+
+ +
+
+

+ Nvidia currently dominates the GPU hardware market, with + a market share over 97%. This has led some to argue... +

+
+

+ From space: GPU GOATS +

+
+
+
+ +
+
+

+ Nvidia currently dominates the GPU hardware market, with + a market share over 97%. This has led some to argue... +

+
+

+ Page url: + https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia +

+
+
+
+
+
+ + {/*
*/} +
+ +
+ {/*
*/} +
+ +
+ +
+
+
+
+
+ ); +} + +export default page; diff --git a/apps/web/app/(canvas)/canvasStyles.css b/apps/web/app/(canvas)/canvasStyles.css new file mode 100644 index 00000000..a53d8c96 --- /dev/null +++ b/apps/web/app/(canvas)/canvasStyles.css @@ -0,0 +1,24 @@ +.tl-background { + background: #1F2428 !important; +} + +.tlui-style-panel.tlui-style-panel__wrapper, .tlui-navigation-panel::before ,.tlui-menu-zone, .tlui-toolbar__tools, .tlui-popover__content, .tlui-menu, .tlui-button__help, .tlui-help-menu, .tlui-dialog__content { + background: #2C3439 !important; + border-top: #2C3439 !important; + border-right: #2C3439 !important; + border-bottom: #2C3439 !important; + border-left: #2C3439 !important; +} + +.tlui-navigation-panel::before { + border-top: #2C3439 !important; + border-right: #2C3439 !important; +} + +.tlui-minimap { + background: #2C3439 !important; +} + +.tlui-minimap__canvas { + background: #1F2428 !important; +} \ No newline at end of file diff --git a/apps/web/app/(canvas)/enabledComp.tsx b/apps/web/app/(canvas)/enabledComp.tsx new file mode 100644 index 00000000..5dbe6ee7 --- /dev/null +++ b/apps/web/app/(canvas)/enabledComp.tsx @@ -0,0 +1,22 @@ +import { TLUiComponents } from "tldraw"; + +export const components: Partial = { + ActionsMenu: null, + MainMenu: null, + QuickActions: null, + TopPanel: null, + DebugPanel: null, + DebugMenu: null, + // Minimap: null, + // ContextMenu: null, + // HelpMenu: null, + // ZoomMenu: null, + // StylePanel: null, + // PageMenu: null, + // NavigationPanel: null, + // Toolbar: null, + // KeyboardShortcutsDialog: null, + // HelperButtons: null, + // SharePanel: null, + // MenuPanel: null, +}; \ No newline at end of file diff --git a/apps/web/app/(canvas)/lib/createAssetUrl.ts b/apps/web/app/(canvas)/lib/createAssetUrl.ts new file mode 100644 index 00000000..be2999db --- /dev/null +++ b/apps/web/app/(canvas)/lib/createAssetUrl.ts @@ -0,0 +1,73 @@ +import { AssetRecordType, TLAsset, getHashForString, truncateStringWithEllipsis } from 'tldraw' +// import { BOOKMARK_ENDPOINT } from './config' + +interface ResponseBody { + title?: string + description?: string + image?: string +} + +export async function createAssetFromUrl({ url }: { type: 'url'; url: string }): Promise { + // try { + // // First, try to get the meta data from our endpoint + // const meta = (await ( + // await fetch(BOOKMARK_ENDPOINT, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ + // url, + // }), + // }) + // ).json()) as ResponseBody + + // return { + // id: AssetRecordType.createId(getHashForString(url)), + // typeName: 'asset', + // type: 'bookmark', + // props: { + // src: url, + // description: meta.description ?? '', + // image: meta.image ?? '', + // title: meta.title ?? truncateStringWithEllipsis(url, 32), + // }, + // meta: {}, + // } + // } catch (error) { + // Otherwise, fallback to fetching data from the url + + let meta: { image: string; title: string; description: string } + + try { + const resp = await fetch(url, { method: 'GET', mode: 'no-cors' }) + const html = await resp.text() + const doc = new DOMParser().parseFromString(html, 'text/html') + meta = { + image: doc.head.querySelector('meta[property="og:image"]')?.getAttribute('content') ?? '', + title: + doc.head.querySelector('meta[property="og:title"]')?.getAttribute('content') ?? + truncateStringWithEllipsis(url, 32), + description: + doc.head.querySelector('meta[property="og:description"]')?.getAttribute('content') ?? '', + } + } catch (error) { + console.error(error) + meta = { image: '', title: truncateStringWithEllipsis(url, 32), description: '' } + } + + // Create the bookmark asset from the meta + return { + id: AssetRecordType.createId(getHashForString(url)), + typeName: 'asset', + type: 'bookmark', + props: { + src: url, + image: meta.image, + title: meta.title, + description: meta.description, + }, + meta: {}, + } + // } +} \ No newline at end of file diff --git a/apps/web/app/(canvas)/lib/createEmbeds.ts b/apps/web/app/(canvas)/lib/createEmbeds.ts new file mode 100644 index 00000000..322e697e --- /dev/null +++ b/apps/web/app/(canvas)/lib/createEmbeds.ts @@ -0,0 +1,134 @@ +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 = await (await fetch(`https://unfurl-bookmark.pruthvirajthinks.workers.dev/?url=${url}`)).json() + 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 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 +} \ No newline at end of file diff --git a/apps/web/app/(canvas)/lib/loadSnap.ts b/apps/web/app/(canvas)/lib/loadSnap.ts new file mode 100644 index 00000000..15aad998 --- /dev/null +++ b/apps/web/app/(canvas)/lib/loadSnap.ts @@ -0,0 +1,13 @@ +import { createTLStore, defaultShapeUtils } from "tldraw"; +import { twitterCardUtil } from "../twitterCard"; +export async function loadRemoteSnapshot() { + const res = await fetch( + "https://learning-cf.pruthvirajthinks.workers.dev/get/page3", + ); + const snapshot = JSON.parse(await res.json()); + const newStore = createTLStore({ + shapeUtils: [...defaultShapeUtils, twitterCardUtil], + }); + newStore.loadSnapshot(snapshot); + return newStore; +} \ No newline at end of file diff --git a/apps/web/app/(canvas)/savesnap.tsx b/apps/web/app/(canvas)/savesnap.tsx new file mode 100644 index 00000000..f82e97e3 --- /dev/null +++ b/apps/web/app/(canvas)/savesnap.tsx @@ -0,0 +1,43 @@ +import { useCallback, useEffect, useState } from "react"; +import { debounce, useEditor } from "tldraw"; + +export function SaveStatus() { + const [save, setSave] = useState("saved!"); + const editor = useEditor(); + + const debouncedSave = useCallback( + debounce(async () => { + const snapshot = editor.store.getSnapshot(); + localStorage.setItem("saved", JSON.stringify(snapshot)); + + const res = await fetch( + "https://learning-cf.pruthvirajthinks.workers.dev/post/page3", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + data: snapshot, + }), + }, + ); + + console.log(await res.json()); + setSave("saved!"); + }, 3000), + [editor], // Dependency array ensures the function is not recreated on every render + ); + + useEffect(() => { + const unsubscribe = editor.store.listen( + () => { + setSave("saving..."); + debouncedSave(); + }, + { scope: "document", source: "user" }, + ); + + return () => unsubscribe(); // Cleanup on unmount + }, [editor, debouncedSave]); + + return ; +} \ No newline at end of file diff --git a/apps/web/app/(canvas)/svg.tsx b/apps/web/app/(canvas)/svg.tsx new file mode 100644 index 00000000..bae4e614 --- /dev/null +++ b/apps/web/app/(canvas)/svg.tsx @@ -0,0 +1,97 @@ +export function SettingsSvg() { + return ( + + + + ); +} + +export function DragSvg() { + return ( + + + + ); +} + +export function TextLoadingSvg() { + return ( + + + + ); +} + +export function ThreeDBlock() { + return ( + + + + ); +} + +export function LinkSvg() { + return ( + + + + ); +} diff --git a/apps/web/app/(canvas)/twitterCard.tsx b/apps/web/app/(canvas)/twitterCard.tsx new file mode 100644 index 00000000..c5582a98 --- /dev/null +++ b/apps/web/app/(canvas)/twitterCard.tsx @@ -0,0 +1,84 @@ +import { BaseBoxShapeUtil, HTMLContainer, TLBaseShape, toDomPrecision } from "tldraw"; + +type ITwitterCardShape = TLBaseShape< + "Twittercard", + { w: number; h: number; url: string } +>; + +export class twitterCardUtil extends BaseBoxShapeUtil { + static override type = "Twittercard" as const; + + getDefaultProps(): ITwitterCardShape["props"] { + return { + w: 500, + h: 550, + url: "", + }; + } + + component(s: ITwitterCardShape) { + return ( + + + + ); + } + + indicator(shape: ITwitterCardShape) { + return ; + } +} + +function TwitterPost({ + isInteractive, + width, + height, + url, +}: { + isInteractive: boolean; + width: number; + height: number; + url: string; +}) { + const link = (() => { + try { + const urlObj = new URL(url); + const path = urlObj.pathname; + return path; + } catch (error) { + console.error("Invalid URL", error); + return null; + } + })(); + + return ( +