diff options
| author | codetorso <[email protected]> | 2024-07-25 23:34:45 +0530 |
|---|---|---|
| committer | codetorso <[email protected]> | 2024-07-25 23:34:45 +0530 |
| commit | 5def889576184917ec7aadee2759eedff4250c2a (patch) | |
| tree | 280ec02942cf1e5f82b25aa82f2fd03871a958f9 /apps/web | |
| parent | fix error on drop (diff) | |
| download | supermemory-5def889576184917ec7aadee2759eedff4250c2a.tar.xz supermemory-5def889576184917ec7aadee2759eedff4250c2a.zip | |
done!!
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/components/canvas/resizablelayout.tsx | 7 | ||||
| -rw-r--r-- | apps/web/components/canvas/sidepanel.tsx | 13 | ||||
| -rw-r--r-- | apps/web/components/canvas/sidepanelcard.tsx | 53 | ||||
| -rw-r--r-- | apps/web/components/canvas/tldrawComponent.tsx | 3 | ||||
| -rw-r--r-- | apps/web/components/canvas/tldrawDrop.tsx | 4 | ||||
| -rw-r--r-- | apps/web/lib/ExternalDroppedContent.ts | 200 | ||||
| -rw-r--r-- | apps/web/lib/createEmbeds.ts | 88 | ||||
| -rw-r--r-- | apps/web/lib/drophelpers.ts | 60 |
8 files changed, 236 insertions, 192 deletions
diff --git a/apps/web/components/canvas/resizablelayout.tsx b/apps/web/components/canvas/resizablelayout.tsx index c8f0cc6e..7429e99a 100644 --- a/apps/web/components/canvas/resizablelayout.tsx +++ b/apps/web/components/canvas/resizablelayout.tsx @@ -6,6 +6,7 @@ import Sidepanel from "./sidepanel"; import Image from "next/image"; import { DragIcon } from "@repo/ui/icons"; import { useRef, useState } from "react"; +import { ChevronRight } from "lucide-react"; export default function ResizableLayout({ id }: { id: string }) { const panelGroupRef = useRef(null); @@ -19,7 +20,7 @@ export default function ResizableLayout({ id }: { id: string }) { return ( <PanelGroup - className="text-white h-screen py-[1vh] px-[1vh] bg-[#111417]" + className="text-white h-screen py-[1vh] px-[1vh] bg-[#171B1F]" direction="horizontal" ref={panelGroupRef} onLayout={(sizes) => { @@ -31,9 +32,9 @@ export default function ResizableLayout({ id }: { id: string }) { </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`} + className={`rounded-lg bg-[#2F363B] absolute top-1/2 z-[1000000] -translate-y-1/2 transition-all py-2 ${!isLeftPanelCollapsed && "px-1"}`} > - <Image src={DragIcon} alt="drag-icon" /> + {isLeftPanelCollapsed ? <ChevronRight className="text-[#989EA4] h-6 w-4 cursor-pointer" /> : <Image src={DragIcon} alt="drag-icon" />} </div> </PanelResizeHandle> <Panel defaultSize={70} minSize={60}> diff --git a/apps/web/components/canvas/sidepanel.tsx b/apps/web/components/canvas/sidepanel.tsx index ff5661f8..89699eb6 100644 --- a/apps/web/components/canvas/sidepanel.tsx +++ b/apps/web/components/canvas/sidepanel.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import Card from "./sidepanelcard"; import { sourcesZod } from "@repo/shared-types"; import { toast } from "sonner"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; type card = { title: string; @@ -17,7 +18,7 @@ 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"> + <div className="flex 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" @@ -25,10 +26,6 @@ function Sidepanel() { <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"> @@ -47,6 +44,7 @@ function Sidepanel() { function Search({ setContent }: { setContent: (e: any) => void }) { return ( <form + className="flex justify-between items-center rounded-md bg-[#121718]" action={async (FormData) => { const search = FormData.get("search") as string; @@ -91,9 +89,12 @@ function Search({ setContent }: { setContent: (e: any) => void }) { <input name="search" placeholder="search memories..." - className="rounded-md w-full bg-[#121718] p-3 text-lg outline-none" + className="w-full bg-transparent p-3 text-lg outline-none flex-grow" type="text" /> + <button type="submit" className=" border-l-2 border-gray-600 h-full p-3"> + <MagnifyingGlassIcon className=" h-5 text-[#fff]" /> + </button> </form> ); } diff --git a/apps/web/components/canvas/sidepanelcard.tsx b/apps/web/components/canvas/sidepanelcard.tsx index 606c7ed8..28b4ebcb 100644 --- a/apps/web/components/canvas/sidepanelcard.tsx +++ b/apps/web/components/canvas/sidepanelcard.tsx @@ -3,6 +3,7 @@ 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({ @@ -17,15 +18,32 @@ export default function Card({ source?: string; }) { const [ref, bounds] = useMeasure(); - const [isDragging, setIsDragging] = useState(false); - const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => { + const handleDragStart = (event: React.DragEvent<HTMLDivElement>, dragSource: 'icon' | 'link' | 'parent') => { setIsDragging(true); - event.dataTransfer.setData( - "application/json", - JSON.stringify({ type, title, content, source }) - ); + + switch(dragSource) { + case 'icon': + event.dataTransfer.setData("application/json", JSON.stringify({ + type, + title, + content, + text: true + })); + break; + case 'link': + event.dataTransfer.setData("text/plain", source || ''); + break; + case 'parent': + event.dataTransfer.setData("application/json", JSON.stringify({ + type, + title, + content, + text: false + })); + break; + } }; const handleDragEnd = () => { @@ -37,13 +55,21 @@ export default function Card({ <div draggable onDragEnd={handleDragEnd} - onDragStart={handleDragStart} + onDragStart={(e) => handleDragStart(e, 'parent')} 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"}`}> + <a + href={source} + className={`${source && "cursor-pointer"}`} + draggable + onDragStart={(e) => { + e.stopPropagation(); + handleDragStart(e, 'icon'); + }} + > <Icon type={type} /> </a> <div> @@ -51,6 +77,17 @@ export default function Card({ <p className="group-hover:line-clamp-[12] transition-all line-clamp-3 text-gray-200"> {content} </p> + <a + draggable + href={source} + className="line-clamp-[1] text-blue-500" + onDragStart={(e) => { + e.stopPropagation(); + handleDragStart(e, 'link'); + }} + > + {source} + </a> </div> </div> </div> diff --git a/apps/web/components/canvas/tldrawComponent.tsx b/apps/web/components/canvas/tldrawComponent.tsx index 316722df..8c66a3e1 100644 --- a/apps/web/components/canvas/tldrawComponent.tsx +++ b/apps/web/components/canvas/tldrawComponent.tsx @@ -12,9 +12,8 @@ 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"; +import createEmbedsFromUrl from "@/lib/createEmbeds"; interface DragContextType { isDraggingOver: boolean; diff --git a/apps/web/components/canvas/tldrawDrop.tsx b/apps/web/components/canvas/tldrawDrop.tsx index 38f2d479..cfbc84f7 100644 --- a/apps/web/components/canvas/tldrawDrop.tsx +++ b/apps/web/components/canvas/tldrawDrop.tsx @@ -6,10 +6,10 @@ import { useEditor } from "tldraw"; import { DragContext } from "./tldrawComponent"; type CardData = { - type: any; // Adjust this according to your actual type title: string; + type: string; content: string; - url: string; + text: boolean; }; function DropZone() { diff --git a/apps/web/lib/ExternalDroppedContent.ts b/apps/web/lib/ExternalDroppedContent.ts index c1f35b36..c6ed1091 100644 --- a/apps/web/lib/ExternalDroppedContent.ts +++ b/apps/web/lib/ExternalDroppedContent.ts @@ -1,103 +1,5 @@ -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, - }, - }, - ]); - }); -} +import {Editor} from "tldraw"; +import createEmbedsFromUrl from "./createEmbeds"; function processURL(input: string): string | null { let str = input.trim(); @@ -116,6 +18,19 @@ function processURL(input: string): string | null { } } +function formatContent(title: string, content: string): string { + const totalLength = title.length + content.length; + const totalHeight = Math.ceil(totalLength * (2 / 3)); + const titleLines = Math.ceil(totalHeight * (2 / 5)); + const contentLines = totalHeight - titleLines; + + const titleWithNewLines = title + '\n'.repeat(titleLines); + const contentWithNewLines = content + '\n'.repeat(contentLines); + + return `${titleWithNewLines.trim()}\n\n${contentWithNewLines.trim()}`; +} + + function formatTextToRatio(text: string): { height: number; width: number } { const RATIO = 4 / 3; const FONT_SIZE = 15; @@ -162,9 +77,8 @@ function formatTextToRatio(text: string): { height: number; width: number } { type CardData = { title: string; type: string; - source: string; content: string; - numChunks: string; + text: boolean; }; type DroppedData = CardData | string | { imageUrl: string }; @@ -188,17 +102,20 @@ export function handleExternalDroppedContent({ createTextCard(editor, position, droppedData, "String Content"); } } else if ("imageUrl" in droppedData) { - // Handle image URL (implementation not provided in original code) } else { - const { content, title, source, type } = droppedData; - const processedURL = processURL(source); - // if (processedURL) { - // createEmbedsFromUrl({ editor, url: processedURL }); - // } else { - // } - createTextCard(editor, position, title, type, content); - } -} + const { content, title, type, text } = droppedData; + if (text){ + const { height, width } = formatTextToRatio(content); + editor.createShape({ + type: 'text', + x: position.x - width / 2, + y: position.y - height / 2, + props: { text: formatContent(title, content) }, + }) + }else { + createTextCard(editor, position, title, type, content); + } +}} function createTextCard(editor: Editor, position: { x: number; y: number }, content: string, type: string, extraInfo: string = "") { const { height, width } = formatTextToRatio(content); @@ -209,62 +126,3 @@ function createTextCard(editor: Editor, position: { x: number; y: number }, cont props: { content, type, extrainfo: extraInfo, h: 200, w: 400 }, }); } - -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; -} diff --git a/apps/web/lib/createEmbeds.ts b/apps/web/lib/createEmbeds.ts new file mode 100644 index 00000000..0a00bcbc --- /dev/null +++ b/apps/web/lib/createEmbeds.ts @@ -0,0 +1,88 @@ +import { AssetRecordType, Editor, TLAsset, TLAssetId, TLExternalContentSource, VecLike, getEmbedInfo, getHashForString } from "tldraw"; +import { createEmptyBookmarkShape } from "./drophelpers"; +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, + }, + }, + ]); + }); +}
\ No newline at end of file diff --git a/apps/web/lib/drophelpers.ts b/apps/web/lib/drophelpers.ts new file mode 100644 index 00000000..29a4c9ad --- /dev/null +++ b/apps/web/lib/drophelpers.ts @@ -0,0 +1,60 @@ +import { Editor, TLBookmarkShape, TLShapePartial, Vec, VecLike, createShapeId } from "tldraw"; + +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; +} + +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(); + } +}
\ No newline at end of file |