From 8e1672a6bdeecb1634faa9b61f4bdea42a5f6612 Mon Sep 17 00:00:00 2001 From: Dhravya Shah Date: Tue, 9 Sep 2025 20:14:38 -0700 Subject: [PATCH] small changes --- .gitignore | 3 +- apps/browser-extension/wxt.config.ts | 5 + .../hooks/use-graph-interactions.ts | 505 +++++++++--------- 3 files changed, 261 insertions(+), 252 deletions(-) diff --git a/.gitignore b/.gitignore index b9a56132..35e991e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. .open-next/ .sentryclirc +drizzle.config.ts # Dependencies node_modules @@ -40,4 +41,4 @@ yarn-error.log* *.pem .venv -__pycache__ \ No newline at end of file +__pycache__ diff --git a/apps/browser-extension/wxt.config.ts b/apps/browser-extension/wxt.config.ts index 0ef00bd2..a6c4bfbd 100644 --- a/apps/browser-extension/wxt.config.ts +++ b/apps/browser-extension/wxt.config.ts @@ -11,6 +11,7 @@ export default defineConfig({ manifest: { name: "supermemory", homepage_url: "https://supermemory.ai", +<<<<<<< Updated upstream version: "6.0.000", permissions: [ "contextMenus", @@ -19,6 +20,10 @@ export default defineConfig({ "webRequest", "tabs", ], +======= + version: "6.0.001", + permissions: ["contextMenus", "storage", "activeTab", "webRequest", "tabs"], +>>>>>>> Stashed changes host_permissions: [ "*://x.com/*", "*://twitter.com/*", diff --git a/packages/ui/memory-graph/hooks/use-graph-interactions.ts b/packages/ui/memory-graph/hooks/use-graph-interactions.ts index 6f0317d2..ec44e83e 100644 --- a/packages/ui/memory-graph/hooks/use-graph-interactions.ts +++ b/packages/ui/memory-graph/hooks/use-graph-interactions.ts @@ -1,48 +1,48 @@ -"use client" +"use client"; -import { useCallback, useRef, useState } from "react" -import { GRAPH_SETTINGS } from "../constants" -import type { GraphNode } from "../types" +import { useCallback, useRef, useState } from "react"; +import { GRAPH_SETTINGS } from "../constants"; +import type { GraphNode } from "../types"; export function useGraphInteractions( variant: "console" | "consumer" = "console", ) { - const settings = GRAPH_SETTINGS[variant] + const settings = GRAPH_SETTINGS[variant]; - const [panX, setPanX] = useState(settings.initialPanX) - const [panY, setPanY] = useState(settings.initialPanY) - const [zoom, setZoom] = useState(settings.initialZoom) - const [isPanning, setIsPanning] = useState(false) - const [panStart, setPanStart] = useState({ x: 0, y: 0 }) - const [hoveredNode, setHoveredNode] = useState(null) - const [selectedNode, setSelectedNode] = useState(null) - const [draggingNodeId, setDraggingNodeId] = useState(null) + const [panX, setPanX] = useState(settings.initialPanX); + const [panY, setPanY] = useState(settings.initialPanY); + const [zoom, setZoom] = useState(settings.initialZoom); + const [isPanning, setIsPanning] = useState(false); + const [panStart, setPanStart] = useState({ x: 0, y: 0 }); + const [hoveredNode, setHoveredNode] = useState(null); + const [selectedNode, setSelectedNode] = useState(null); + const [draggingNodeId, setDraggingNodeId] = useState(null); const [dragStart, setDragStart] = useState({ x: 0, y: 0, nodeX: 0, nodeY: 0, - }) + }); const [nodePositions, setNodePositions] = useState< Map - >(new Map()) - + >(new Map()); + // Touch gesture state const [touchState, setTouchState] = useState<{ - touches: { id: number; x: number; y: number }[] - lastDistance: number - lastCenter: { x: number; y: number } - isGesturing: boolean + touches: { id: number; x: number; y: number }[]; + lastDistance: number; + lastCenter: { x: number; y: number }; + isGesturing: boolean; }>({ touches: [], lastDistance: 0, lastCenter: { x: 0, y: 0 }, isGesturing: false, - }) + }); // Animation state for smooth transitions - const animationRef = useRef(null) - const [isAnimating, setIsAnimating] = useState(false) + const animationRef = useRef(null); + const [isAnimating, setIsAnimating] = useState(false); // Smooth animation helper const animateToViewState = useCallback( @@ -50,46 +50,46 @@ export function useGraphInteractions( targetPanX: number, targetPanY: number, targetZoom: number, - duration: number = 300, + duration = 300, ) => { if (animationRef.current) { - cancelAnimationFrame(animationRef.current) + cancelAnimationFrame(animationRef.current); } - const startPanX = panX - const startPanY = panY - const startZoom = zoom - const startTime = Date.now() - - setIsAnimating(true) + const startPanX = panX; + const startPanY = panY; + const startZoom = zoom; + const startTime = Date.now(); + + setIsAnimating(true); const animate = () => { - const elapsed = Date.now() - startTime - const progress = Math.min(elapsed / duration, 1) - + const elapsed = Date.now() - startTime; + const progress = Math.min(elapsed / duration, 1); + // Ease out cubic function for smooth transitions - const easeOut = 1 - Math.pow(1 - progress, 3) - - const currentPanX = startPanX + (targetPanX - startPanX) * easeOut - const currentPanY = startPanY + (targetPanY - startPanY) * easeOut - const currentZoom = startZoom + (targetZoom - startZoom) * easeOut - - setPanX(currentPanX) - setPanY(currentPanY) - setZoom(currentZoom) - + const easeOut = 1 - (1 - progress) ** 3; + + const currentPanX = startPanX + (targetPanX - startPanX) * easeOut; + const currentPanY = startPanY + (targetPanY - startPanY) * easeOut; + const currentZoom = startZoom + (targetZoom - startZoom) * easeOut; + + setPanX(currentPanX); + setPanY(currentPanY); + setZoom(currentZoom); + if (progress < 1) { - animationRef.current = requestAnimationFrame(animate) + animationRef.current = requestAnimationFrame(animate); } else { - setIsAnimating(false) - animationRef.current = null + setIsAnimating(false); + animationRef.current = null; } - } - - animate() + }; + + animate(); }, [panX, panY, zoom], - ) + ); // Node drag handlers const handleNodeDragStart = useCallback( @@ -158,107 +158,107 @@ export function useGraphInteractions( const handleWheel = useCallback( (e: React.WheelEvent) => { // Always prevent default to stop browser navigation - e.preventDefault() - e.stopPropagation() - + e.preventDefault(); + e.stopPropagation(); + // Handle horizontal scrolling (trackpad swipe) by converting to pan if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) { // Horizontal scroll - pan the graph instead of zooming - const panDelta = e.deltaX * 0.5 - setPanX(prev => prev - panDelta) - return + const panDelta = e.deltaX * 0.5; + setPanX((prev) => prev - panDelta); + return; } - + // Vertical scroll - zoom behavior - const delta = e.deltaY > 0 ? 0.97 : 1.03 - const newZoom = Math.max(0.05, Math.min(3, zoom * delta)) - + const delta = e.deltaY > 0 ? 0.97 : 1.03; + const newZoom = Math.max(0.05, Math.min(3, zoom * delta)); + // Get mouse position relative to the viewport - let mouseX = e.clientX - let mouseY = e.clientY - + let mouseX = e.clientX; + let mouseY = e.clientY; + // Try to get the container bounds to make coordinates relative to the graph container - const target = e.currentTarget - if (target && 'getBoundingClientRect' in target) { - const rect = target.getBoundingClientRect() - mouseX = e.clientX - rect.left - mouseY = e.clientY - rect.top + const target = e.currentTarget; + if (target && "getBoundingClientRect" in target) { + const rect = target.getBoundingClientRect(); + mouseX = e.clientX - rect.left; + mouseY = e.clientY - rect.top; } - + // Calculate the world position of the mouse cursor - const worldX = (mouseX - panX) / zoom - const worldY = (mouseY - panY) / zoom - + const worldX = (mouseX - panX) / zoom; + const worldY = (mouseY - panY) / zoom; + // Calculate new pan to keep the mouse position stationary - const newPanX = mouseX - worldX * newZoom - const newPanY = mouseY - worldY * newZoom - - setZoom(newZoom) - setPanX(newPanX) - setPanY(newPanY) + const newPanX = mouseX - worldX * newZoom; + const newPanY = mouseY - worldY * newZoom; + + setZoom(newZoom); + setPanX(newPanX); + setPanY(newPanY); }, [zoom, panX, panY], - ) + ); const zoomIn = useCallback( - (centerX?: number, centerY?: number, animate: boolean = true) => { - const zoomFactor = 1.2 - const newZoom = Math.min(3, zoom * zoomFactor) // Increased max zoom to 3x - + (centerX?: number, centerY?: number, animate = true) => { + const zoomFactor = 1.2; + const newZoom = Math.min(3, zoom * zoomFactor); // Increased max zoom to 3x + if (centerX !== undefined && centerY !== undefined) { // Mouse-centered zoom for programmatic zoom in - const worldX = (centerX - panX) / zoom - const worldY = (centerY - panY) / zoom - const newPanX = centerX - worldX * newZoom - const newPanY = centerY - worldY * newZoom - + const worldX = (centerX - panX) / zoom; + const worldY = (centerY - panY) / zoom; + const newPanX = centerX - worldX * newZoom; + const newPanY = centerY - worldY * newZoom; + if (animate && !isAnimating) { - animateToViewState(newPanX, newPanY, newZoom, 200) + animateToViewState(newPanX, newPanY, newZoom, 200); } else { - setZoom(newZoom) - setPanX(newPanX) - setPanY(newPanY) + setZoom(newZoom); + setPanX(newPanX); + setPanY(newPanY); } } else { if (animate && !isAnimating) { - animateToViewState(panX, panY, newZoom, 200) + animateToViewState(panX, panY, newZoom, 200); } else { - setZoom(newZoom) + setZoom(newZoom); } } }, [zoom, panX, panY, isAnimating, animateToViewState], - ) + ); const zoomOut = useCallback( - (centerX?: number, centerY?: number, animate: boolean = true) => { - const zoomFactor = 0.8 - const newZoom = Math.max(0.05, zoom * zoomFactor) // Decreased min zoom to 0.05x - + (centerX?: number, centerY?: number, animate = true) => { + const zoomFactor = 0.8; + const newZoom = Math.max(0.05, zoom * zoomFactor); // Decreased min zoom to 0.05x + if (centerX !== undefined && centerY !== undefined) { // Mouse-centered zoom for programmatic zoom out - const worldX = (centerX - panX) / zoom - const worldY = (centerY - panY) / zoom - const newPanX = centerX - worldX * newZoom - const newPanY = centerY - worldY * newZoom - + const worldX = (centerX - panX) / zoom; + const worldY = (centerY - panY) / zoom; + const newPanX = centerX - worldX * newZoom; + const newPanY = centerY - worldY * newZoom; + if (animate && !isAnimating) { - animateToViewState(newPanX, newPanY, newZoom, 200) + animateToViewState(newPanX, newPanY, newZoom, 200); } else { - setZoom(newZoom) - setPanX(newPanX) - setPanY(newPanY) + setZoom(newZoom); + setPanX(newPanX); + setPanY(newPanY); } } else { if (animate && !isAnimating) { - animateToViewState(panX, panY, newZoom, 200) + animateToViewState(panX, panY, newZoom, 200); } else { - setZoom(newZoom) + setZoom(newZoom); } } }, [zoom, panX, panY, isAnimating, animateToViewState], - ) + ); const resetView = useCallback(() => { setPanX(settings.initialPanX); @@ -278,10 +278,10 @@ export function useGraphInteractions( if (nodes.length === 0) return; // Find the bounds of all nodes - let minX = Number.POSITIVE_INFINITY, - maxX = Number.NEGATIVE_INFINITY; - let minY = Number.POSITIVE_INFINITY, - maxY = Number.NEGATIVE_INFINITY; + let minX = Number.POSITIVE_INFINITY; + let maxX = Number.NEGATIVE_INFINITY; + let minY = Number.POSITIVE_INFINITY; + let maxY = Number.NEGATIVE_INFINITY; nodes.forEach((node) => { minX = Math.min(minX, node.x - node.size / 2); @@ -308,178 +308,181 @@ export function useGraphInteractions( const availableWidth = Math.max(1, viewportWidth - occludedRightPx); // Calculate the zoom needed to fit the content within available width - const zoomX = availableWidth / paddedWidth - const zoomY = viewportHeight / paddedHeight - const newZoom = Math.min(Math.max(0.05, Math.min(zoomX, zoomY)), 3) + const zoomX = availableWidth / paddedWidth; + const zoomY = viewportHeight / paddedHeight; + const newZoom = Math.min(Math.max(0.05, Math.min(zoomX, zoomY)), 3); // Calculate pan to center the content within available area - const availableCenterX = availableWidth / 2 - const newPanX = availableCenterX - contentCenterX * newZoom - const newPanY = viewportHeight / 2 - contentCenterY * newZoom + const availableCenterX = availableWidth / 2; + const newPanX = availableCenterX - contentCenterX * newZoom; + const newPanY = viewportHeight / 2 - contentCenterY * newZoom; // Apply the new view (optional animation) if (options?.animate) { - const steps = 8 - const durationMs = 160 // snappy - const intervalMs = Math.max(1, Math.floor(durationMs / steps)) - const startZoom = zoom - const startPanX = panX - const startPanY = panY - let i = 0 - const ease = (t: number) => 1 - (1 - t) ** 2 // ease-out quad + const steps = 8; + const durationMs = 160; // snappy + const intervalMs = Math.max(1, Math.floor(durationMs / steps)); + const startZoom = zoom; + const startPanX = panX; + const startPanY = panY; + let i = 0; + const ease = (t: number) => 1 - (1 - t) ** 2; // ease-out quad const timer = setInterval(() => { - i++ - const t = ease(i / steps) - setZoom(startZoom + (newZoom - startZoom) * t) - setPanX(startPanX + (newPanX - startPanX) * t) - setPanY(startPanY + (newPanY - startPanY) * t) - if (i >= steps) clearInterval(timer) - }, intervalMs) + i++; + const t = ease(i / steps); + setZoom(startZoom + (newZoom - startZoom) * t); + setPanX(startPanX + (newPanX - startPanX) * t); + setPanY(startPanY + (newPanY - startPanY) * t); + if (i >= steps) clearInterval(timer); + }, intervalMs); } else { - setZoom(newZoom) - setPanX(newPanX) - setPanY(newPanY) + setZoom(newZoom); + setPanX(newPanX); + setPanY(newPanY); } }, [zoom, panX, panY], - ) + ); // Touch gesture handlers for mobile pinch-to-zoom const handleTouchStart = useCallback((e: React.TouchEvent) => { - const touches = Array.from(e.touches).map(touch => ({ + const touches = Array.from(e.touches).map((touch) => ({ id: touch.identifier, x: touch.clientX, y: touch.clientY, - })) + })); if (touches.length >= 2) { // Start gesture with two or more fingers - const touch1 = touches[0]! - const touch2 = touches[1]! - + const touch1 = touches[0]!; + const touch2 = touches[1]!; + const distance = Math.sqrt( - Math.pow(touch2.x - touch1.x, 2) + Math.pow(touch2.y - touch1.y, 2) - ) - + (touch2.x - touch1.x) ** 2 + (touch2.y - touch1.y) ** 2, + ); + const center = { x: (touch1.x + touch2.x) / 2, y: (touch1.y + touch2.y) / 2, - } - + }; + setTouchState({ touches, lastDistance: distance, lastCenter: center, isGesturing: true, - }) + }); } else { - setTouchState(prev => ({ ...prev, touches, isGesturing: false })) + setTouchState((prev) => ({ ...prev, touches, isGesturing: false })); } - }, []) + }, []); - const handleTouchMove = useCallback((e: React.TouchEvent) => { - e.preventDefault() - - const touches = Array.from(e.touches).map(touch => ({ - id: touch.identifier, - x: touch.clientX, - y: touch.clientY, - })) + const handleTouchMove = useCallback( + (e: React.TouchEvent) => { + e.preventDefault(); - if (touches.length >= 2 && touchState.isGesturing) { - const touch1 = touches[0]! - const touch2 = touches[1]! - - const distance = Math.sqrt( - Math.pow(touch2.x - touch1.x, 2) + Math.pow(touch2.y - touch1.y, 2) - ) - - const center = { - x: (touch1.x + touch2.x) / 2, - y: (touch1.y + touch2.y) / 2, + const touches = Array.from(e.touches).map((touch) => ({ + id: touch.identifier, + x: touch.clientX, + y: touch.clientY, + })); + + if (touches.length >= 2 && touchState.isGesturing) { + const touch1 = touches[0]!; + const touch2 = touches[1]!; + + const distance = Math.sqrt( + (touch2.x - touch1.x) ** 2 + (touch2.y - touch1.y) ** 2, + ); + + const center = { + x: (touch1.x + touch2.x) / 2, + y: (touch1.y + touch2.y) / 2, + }; + + // Calculate zoom change based on pinch distance change + const distanceChange = distance / touchState.lastDistance; + const newZoom = Math.max(0.05, Math.min(3, zoom * distanceChange)); + + // Get canvas bounds for center calculation + const canvas = e.currentTarget as HTMLElement; + const rect = canvas.getBoundingClientRect(); + const centerX = center.x - rect.left; + const centerY = center.y - rect.top; + + // Calculate the world position of the pinch center + const worldX = (centerX - panX) / zoom; + const worldY = (centerY - panY) / zoom; + + // Calculate new pan to keep the pinch center stationary + const newPanX = centerX - worldX * newZoom; + const newPanY = centerY - worldY * newZoom; + + // Calculate pan change based on center movement + const centerDx = center.x - touchState.lastCenter.x; + const centerDy = center.y - touchState.lastCenter.y; + + setZoom(newZoom); + setPanX(newPanX + centerDx); + setPanY(newPanY + centerDy); + + setTouchState({ + touches, + lastDistance: distance, + lastCenter: center, + isGesturing: true, + }); + } else if (touches.length === 1 && !touchState.isGesturing && isPanning) { + // Single finger pan (only if not in gesture mode) + const touch = touches[0]!; + const newPanX = touch.x - panStart.x; + const newPanY = touch.y - panStart.y; + setPanX(newPanX); + setPanY(newPanY); } - - // Calculate zoom change based on pinch distance change - const distanceChange = distance / touchState.lastDistance - const newZoom = Math.max(0.05, Math.min(3, zoom * distanceChange)) - - // Get canvas bounds for center calculation - const canvas = e.currentTarget as HTMLElement - const rect = canvas.getBoundingClientRect() - const centerX = center.x - rect.left - const centerY = center.y - rect.top - - // Calculate the world position of the pinch center - const worldX = (centerX - panX) / zoom - const worldY = (centerY - panY) / zoom - - // Calculate new pan to keep the pinch center stationary - const newPanX = centerX - worldX * newZoom - const newPanY = centerY - worldY * newZoom - - // Calculate pan change based on center movement - const centerDx = center.x - touchState.lastCenter.x - const centerDy = center.y - touchState.lastCenter.y - - setZoom(newZoom) - setPanX(newPanX + centerDx) - setPanY(newPanY + centerDy) - - setTouchState({ - touches, - lastDistance: distance, - lastCenter: center, - isGesturing: true, - }) - } else if (touches.length === 1 && !touchState.isGesturing && isPanning) { - // Single finger pan (only if not in gesture mode) - const touch = touches[0]! - const newPanX = touch.x - panStart.x - const newPanY = touch.y - panStart.y - setPanX(newPanX) - setPanY(newPanY) - } - }, [touchState, zoom, panX, panY, isPanning, panStart]) + }, + [touchState, zoom, panX, panY, isPanning, panStart], + ); const handleTouchEnd = useCallback((e: React.TouchEvent) => { - const touches = Array.from(e.touches).map(touch => ({ + const touches = Array.from(e.touches).map((touch) => ({ id: touch.identifier, x: touch.clientX, y: touch.clientY, - })) + })); if (touches.length < 2) { - setTouchState(prev => ({ ...prev, touches, isGesturing: false })) + setTouchState((prev) => ({ ...prev, touches, isGesturing: false })); } else { - setTouchState(prev => ({ ...prev, touches })) + setTouchState((prev) => ({ ...prev, touches })); } if (touches.length === 0) { - setIsPanning(false) + setIsPanning(false); } - }, []) + }, []); // Center viewport on a specific world position (with animation) const centerViewportOn = useCallback( ( - worldX: number, - worldY: number, - viewportWidth: number, + worldX: number, + worldY: number, + viewportWidth: number, viewportHeight: number, - animate: boolean = true + animate = true, ) => { - const newPanX = viewportWidth / 2 - worldX * zoom - const newPanY = viewportHeight / 2 - worldY * zoom - + const newPanX = viewportWidth / 2 - worldX * zoom; + const newPanY = viewportHeight / 2 - worldY * zoom; + if (animate && !isAnimating) { - animateToViewState(newPanX, newPanY, zoom, 400) + animateToViewState(newPanX, newPanY, zoom, 400); } else { - setPanX(newPanX) - setPanY(newPanY) + setPanX(newPanX); + setPanY(newPanY); } }, [zoom, isAnimating, animateToViewState], - ) + ); // Node interaction handlers const handleNodeHover = useCallback((nodeId: string | null) => { @@ -496,35 +499,35 @@ export function useGraphInteractions( const handleDoubleClick = useCallback( (e: React.MouseEvent) => { // Calculate new zoom (zoom in by 1.5x) - const zoomFactor = 1.5 - const newZoom = Math.min(3, zoom * zoomFactor) + const zoomFactor = 1.5; + const newZoom = Math.min(3, zoom * zoomFactor); // Get mouse position relative to the container - let mouseX = e.clientX - let mouseY = e.clientY - + let mouseX = e.clientX; + let mouseY = e.clientY; + // Try to get the container bounds to make coordinates relative to the graph container - const target = e.currentTarget - if (target && 'getBoundingClientRect' in target) { - const rect = target.getBoundingClientRect() - mouseX = e.clientX - rect.left - mouseY = e.clientY - rect.top + const target = e.currentTarget; + if (target && "getBoundingClientRect" in target) { + const rect = target.getBoundingClientRect(); + mouseX = e.clientX - rect.left; + mouseY = e.clientY - rect.top; } // Calculate the world position of the clicked point - const worldX = (mouseX - panX) / zoom - const worldY = (mouseY - panY) / zoom + const worldX = (mouseX - panX) / zoom; + const worldY = (mouseY - panY) / zoom; // Calculate new pan to keep the clicked point in the same screen position - const newPanX = mouseX - worldX * newZoom - const newPanY = mouseY - worldY * newZoom + const newPanX = mouseX - worldX * newZoom; + const newPanY = mouseY - worldY * newZoom; - setZoom(newZoom) - setPanX(newPanX) - setPanY(newPanY) + setZoom(newZoom); + setPanX(newPanX); + setPanY(newPanY); }, [zoom, panX, panY], - ) + ); return { // State @@ -557,5 +560,5 @@ export function useGraphInteractions( autoFitToViewport, centerViewportOn, setSelectedNode, - } + }; }