aboutsummaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorcodetorso <[email protected]>2024-07-25 23:34:45 +0530
committercodetorso <[email protected]>2024-07-25 23:34:45 +0530
commit5def889576184917ec7aadee2759eedff4250c2a (patch)
tree280ec02942cf1e5f82b25aa82f2fd03871a958f9 /apps/web
parentfix error on drop (diff)
downloadsupermemory-5def889576184917ec7aadee2759eedff4250c2a.tar.xz
supermemory-5def889576184917ec7aadee2759eedff4250c2a.zip
done!!
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/components/canvas/resizablelayout.tsx7
-rw-r--r--apps/web/components/canvas/sidepanel.tsx13
-rw-r--r--apps/web/components/canvas/sidepanelcard.tsx53
-rw-r--r--apps/web/components/canvas/tldrawComponent.tsx3
-rw-r--r--apps/web/components/canvas/tldrawDrop.tsx4
-rw-r--r--apps/web/lib/ExternalDroppedContent.ts200
-rw-r--r--apps/web/lib/createEmbeds.ts88
-rw-r--r--apps/web/lib/drophelpers.ts60
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