mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
added dimming to bg when popover is open.
This commit is contained in:
parent
696f5bbffa
commit
ac8fad9038
5 changed files with 118 additions and 7 deletions
|
|
@ -1,5 +1,49 @@
|
|||
# Memory Graph Changes
|
||||
|
||||
> **Testing Playground:** To test changes, run these 2 commands in separate terminals:
|
||||
>
|
||||
> **Terminal 1** - Build memory-graph in watch mode:
|
||||
> ```bash
|
||||
> cd packages/memory-graph && bun run dev
|
||||
> ```
|
||||
>
|
||||
> **Terminal 2** - Run the playground:
|
||||
> ```bash
|
||||
> cd apps/memory-graph-playground && bun run dev
|
||||
> ```
|
||||
>
|
||||
> Then open http://localhost:3000 in your browser.
|
||||
|
||||
## Visual & Layout Improvements (2025-12-22)
|
||||
|
||||
### Background Dimming When Popover is Open
|
||||
**Feature:** When a popover is opened for a doc/memory node, the background dims while the selected node and popover remain in full focus.
|
||||
|
||||
**Implementation:**
|
||||
- Smooth animated dimming: 200ms ease-out cubic transition
|
||||
- Canvas-based dimming: non-selected nodes reduced to 20% opacity
|
||||
- Edges not connected to selected node reduced to 10% opacity
|
||||
- Selected node remains at full opacity (1.0) for clear focus
|
||||
- Transparent backdrop for click-outside-to-close functionality
|
||||
- Escape key handler to close popover
|
||||
- Popover positioned at z-index 1000
|
||||
|
||||
**User Experience:**
|
||||
- Smooth, polished transition when opening/closing popovers
|
||||
- Creates clear visual hierarchy when inspecting individual nodes
|
||||
- Selected node stays bright and visible
|
||||
- Reduces visual noise from surrounding graph elements
|
||||
- Makes popover content easier to read
|
||||
- Multiple ways to close: click backdrop, click X button, or press Escape key
|
||||
|
||||
**Files Changed:**
|
||||
- `src/components/node-popover.tsx:3,20-29` - Escape key handler and transparent backdrop
|
||||
- `src/components/graph-canvas.tsx:53-54,62-91,289,294,301,306,317,435,454,577` - Smooth animation and dimming logic
|
||||
- `src/components/memory-graph.tsx:620` - Pass selectedNodeId to canvas
|
||||
- `src/types.ts:86` - Added selectedNodeId prop to GraphCanvasProps
|
||||
|
||||
---
|
||||
|
||||
## Visual & Layout Improvements (2025-12-21)
|
||||
|
||||
### Smart Positioning Node Popover
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
import { colors } from "@/constants"
|
||||
import type {
|
||||
|
|
@ -43,18 +44,57 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
draggingNodeId,
|
||||
highlightDocumentIds,
|
||||
isSimulationActive = false,
|
||||
selectedNodeId = null,
|
||||
}) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const animationRef = useRef<number>(0)
|
||||
const startTimeRef = useRef<number>(Date.now())
|
||||
const mousePos = useRef<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||
const currentHoveredNode = useRef<string | null>(null)
|
||||
const dimProgress = useRef<number>(selectedNodeId ? 1 : 0)
|
||||
const dimAnimationRef = useRef<number>(0)
|
||||
const [, forceRender] = useState(0)
|
||||
|
||||
// Initialize start time once
|
||||
useEffect(() => {
|
||||
startTimeRef.current = Date.now()
|
||||
}, [])
|
||||
|
||||
// Smooth dimming animation
|
||||
useEffect(() => {
|
||||
const targetDim = selectedNodeId ? 1 : 0
|
||||
const duration = 200 // milliseconds
|
||||
const startDim = dimProgress.current
|
||||
const startTime = Date.now()
|
||||
|
||||
const animate = () => {
|
||||
const elapsed = Date.now() - startTime
|
||||
const progress = Math.min(elapsed / duration, 1)
|
||||
|
||||
// Ease-out cubic easing for smooth deceleration
|
||||
const eased = 1 - Math.pow(1 - progress, 3)
|
||||
dimProgress.current = startDim + (targetDim - startDim) * eased
|
||||
|
||||
// Force re-render to update canvas during animation
|
||||
forceRender(prev => prev + 1)
|
||||
|
||||
if (progress < 1) {
|
||||
dimAnimationRef.current = requestAnimationFrame(animate)
|
||||
}
|
||||
}
|
||||
|
||||
if (dimAnimationRef.current) {
|
||||
cancelAnimationFrame(dimAnimationRef.current)
|
||||
}
|
||||
animate()
|
||||
|
||||
return () => {
|
||||
if (dimAnimationRef.current) {
|
||||
cancelAnimationFrame(dimAnimationRef.current)
|
||||
}
|
||||
}
|
||||
}, [selectedNodeId])
|
||||
|
||||
// Efficient hit detection
|
||||
const getNodeAtPosition = useCallback(
|
||||
(x: number, y: number): string | null => {
|
||||
|
|
@ -246,22 +286,29 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
}
|
||||
}
|
||||
|
||||
// Check if edge should be dimmed (not connected to selected node)
|
||||
const edgeShouldDim = selectedNodeId !== null &&
|
||||
sourceNode.id !== selectedNodeId &&
|
||||
targetNode.id !== selectedNodeId
|
||||
// Smooth edge opacity: interpolate between full and 0.1 (dimmed)
|
||||
const edgeDimOpacity = 1 - (dimProgress.current * 0.9)
|
||||
|
||||
// Enhanced connection styling based on edge type
|
||||
let connectionColor = colors.connection.weak
|
||||
let dashPattern: number[] = []
|
||||
let opacity = edge.visualProps.opacity
|
||||
let opacity = edgeShouldDim ? edgeDimOpacity : edge.visualProps.opacity
|
||||
let lineWidth = Math.max(1, edge.visualProps.thickness * zoom)
|
||||
|
||||
if (edge.edgeType === "doc-memory") {
|
||||
// Doc-memory: Solid thin lines, subtle
|
||||
dashPattern = []
|
||||
connectionColor = colors.connection.memory
|
||||
opacity = 0.9
|
||||
opacity = edgeShouldDim ? edgeDimOpacity : 0.9
|
||||
lineWidth = 1
|
||||
} else if (edge.edgeType === "doc-doc") {
|
||||
// Doc-doc: Thick dashed lines with strong similarity emphasis
|
||||
dashPattern = useSimplifiedRendering ? [] : [10, 5] // Solid lines when zoomed out
|
||||
opacity = Math.max(0, edge.similarity * 0.5)
|
||||
opacity = edgeShouldDim ? edgeDimOpacity : Math.max(0, edge.similarity * 0.5)
|
||||
lineWidth = Math.max(1, edge.similarity * 2) // Thicker for stronger similarity
|
||||
|
||||
if (edge.similarity > 0.85)
|
||||
|
|
@ -272,7 +319,7 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
// Version chains: Double line effect with relation-specific colors
|
||||
dashPattern = []
|
||||
connectionColor = edge.color || colors.relations.updates
|
||||
opacity = 0.8
|
||||
opacity = edgeShouldDim ? edgeDimOpacity : 0.8
|
||||
lineWidth = 2
|
||||
}
|
||||
|
||||
|
|
@ -389,6 +436,10 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
|
||||
const isHovered = currentHoveredNode.current === node.id
|
||||
const isDragging = node.isDragging
|
||||
const isSelected = selectedNodeId === node.id
|
||||
const shouldDim = selectedNodeId !== null && !isSelected
|
||||
// Smooth opacity: interpolate between 1 (full) and 0.2 (dimmed) based on animation progress
|
||||
const nodeOpacity = shouldDim ? 1 - (dimProgress.current * 0.8) : 1
|
||||
const isHighlightedDocument = (() => {
|
||||
if (node.type !== "document" || highlightSet.size === 0) return false
|
||||
const doc = node.data as DocumentWithMemories
|
||||
|
|
@ -407,7 +458,7 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
: isHovered
|
||||
? colors.document.secondary
|
||||
: colors.document.primary
|
||||
ctx.globalAlpha = 1
|
||||
ctx.globalAlpha = nodeOpacity
|
||||
|
||||
// Enhanced border with subtle glow
|
||||
ctx.strokeStyle = isDragging
|
||||
|
|
@ -530,7 +581,7 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
const radius = nodeSize / 2
|
||||
|
||||
ctx.fillStyle = fillColor
|
||||
ctx.globalAlpha = isLatest ? 1 : 0.4
|
||||
ctx.globalAlpha = shouldDim ? nodeOpacity : (isLatest ? 1 : 0.4)
|
||||
ctx.strokeStyle = borderColor
|
||||
ctx.lineWidth = isDragging ? 3 : isHovered ? 2 : 1.5
|
||||
|
||||
|
|
|
|||
|
|
@ -617,6 +617,7 @@ export const MemoryGraph = ({
|
|||
panY={panY}
|
||||
width={containerSize.width}
|
||||
zoom={zoom}
|
||||
selectedNodeId={selectedNode}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { memo } from "react"
|
||||
import { memo, useEffect } from "react"
|
||||
import type { GraphNode } from "@/types"
|
||||
|
||||
export interface NodePopoverProps {
|
||||
|
|
@ -16,6 +16,18 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({
|
|||
y,
|
||||
onClose,
|
||||
}) {
|
||||
// Handle Escape key to close popover
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown)
|
||||
return () => window.removeEventListener("keydown", handleKeyDown)
|
||||
}, [onClose])
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Invisible backdrop to catch clicks outside */}
|
||||
|
|
@ -26,6 +38,7 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({
|
|||
inset: 0,
|
||||
zIndex: 999,
|
||||
pointerEvents: "auto",
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ export interface GraphCanvasProps {
|
|||
highlightDocumentIds?: string[]
|
||||
// Physics simulation state
|
||||
isSimulationActive?: boolean
|
||||
// Selected node ID - dims all other nodes and edges
|
||||
selectedNodeId?: string | null
|
||||
}
|
||||
|
||||
export interface MemoryGraphProps {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue