mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
added doc type icon to graph nodes for supported file formats. also updated doc hover effect to surround the node instead of overlapping it.
This commit is contained in:
parent
ef59792f7b
commit
72df14c7f7
3 changed files with 312 additions and 12 deletions
|
|
@ -2,6 +2,28 @@
|
|||
|
||||
## Visual & Layout Improvements (2025-12-21)
|
||||
|
||||
### Document Type Icons on Cards
|
||||
**Feature:** Document cards now display type-specific icons centered on the card for better visual identification.
|
||||
|
||||
**Supported Document Types:**
|
||||
- 📄 TXT - Text documents (default for unsupported types)
|
||||
- 📑 PDF - PDF files
|
||||
- 📝 MD - Markdown files
|
||||
- 📘 DOC/DOCX - Word documents
|
||||
- 📄 RTF - Rich Text Format files
|
||||
- 📊 CSV - Comma-separated values (grid icon)
|
||||
- {} JSON - JSON files (curly braces)
|
||||
|
||||
**Implementation:**
|
||||
- Icon rendering uses Canvas 2D API for performance
|
||||
- Icons scale with card size (40% of card height)
|
||||
- Only rendered when zoomed in (skipped in simplified rendering mode)
|
||||
- Centered on document cards for better visual balance
|
||||
|
||||
**Files Changed:**
|
||||
- `src/utils/document-icons.ts` - New utility with canvas-based icon drawing functions
|
||||
- `src/components/graph-canvas.tsx:18,471-487` - Icon rendering integration
|
||||
|
||||
### Updated Color Scheme for Better Visual Clarity
|
||||
**Changes:**
|
||||
- Refined color palette for improved contrast and readability
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import type {
|
|||
GraphNode,
|
||||
MemoryEntry,
|
||||
} from "@/types"
|
||||
import { drawDocumentIcon } from "@/utils/document-icons"
|
||||
import { canvasWrapper } from "./canvas-common.css"
|
||||
|
||||
export const GraphCanvas = memo<GraphCanvasProps>(
|
||||
|
|
@ -64,12 +65,30 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
const screenY = node.y * zoom + panY
|
||||
const nodeSize = node.size * zoom
|
||||
|
||||
const dx = x - screenX
|
||||
const dy = y - screenY
|
||||
const distance = Math.sqrt(dx * dx + dy * dy)
|
||||
if (node.type === "document") {
|
||||
// Rectangular hit detection for documents (matches visual size)
|
||||
const docWidth = nodeSize * 1.4
|
||||
const docHeight = nodeSize * 0.9
|
||||
const halfW = docWidth / 2
|
||||
const halfH = docHeight / 2
|
||||
|
||||
if (distance <= nodeSize / 2) {
|
||||
return node.id
|
||||
if (
|
||||
x >= screenX - halfW &&
|
||||
x <= screenX + halfW &&
|
||||
y >= screenY - halfH &&
|
||||
y <= screenY + halfH
|
||||
) {
|
||||
return node.id
|
||||
}
|
||||
} else {
|
||||
// Circular hit detection for memory nodes
|
||||
const dx = x - screenX
|
||||
const dy = y - screenY
|
||||
const distance = Math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
if (distance <= nodeSize / 2) {
|
||||
return node.id
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
|
@ -433,7 +452,9 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
ctx.strokeStyle = colors.accent.primary
|
||||
ctx.lineWidth = 3
|
||||
ctx.setLineDash([6, 4])
|
||||
const ringPadding = 10
|
||||
// Add equal padding on all sides (15% of average dimension)
|
||||
const avgDimension = (docWidth + docHeight) / 2
|
||||
const ringPadding = avgDimension * 0.1
|
||||
ctx.beginPath()
|
||||
ctx.roundRect(
|
||||
screenX - docWidth / 2 - ringPadding,
|
||||
|
|
@ -446,6 +467,21 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
ctx.setLineDash([])
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
// Draw document type icon (centered)
|
||||
if (!useSimplifiedRendering) {
|
||||
const doc = node.data as DocumentWithMemories
|
||||
const iconSize = docHeight * 0.4 // Icon size relative to card height
|
||||
|
||||
drawDocumentIcon(
|
||||
ctx,
|
||||
screenX,
|
||||
screenY,
|
||||
iconSize,
|
||||
doc.type || "text",
|
||||
"rgba(255, 255, 255, 0.8)",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Enhanced memory styling with status indicators
|
||||
const mem = node.data as MemoryEntry
|
||||
|
|
@ -581,18 +617,23 @@ export const GraphCanvas = memo<GraphCanvasProps>(
|
|||
ctx.globalAlpha = 0.6
|
||||
|
||||
ctx.beginPath()
|
||||
const glowSize = nodeSize * 0.7
|
||||
if (node.type === "document") {
|
||||
// Use actual document dimensions for glow
|
||||
const docWidth = nodeSize * 1.4
|
||||
const docHeight = nodeSize * 0.9
|
||||
// Make glow 10% larger than document
|
||||
const avgDimension = (docWidth + docHeight) / 2
|
||||
const glowPadding = avgDimension * 0.1
|
||||
ctx.roundRect(
|
||||
screenX - glowSize,
|
||||
screenY - glowSize / 1.4,
|
||||
glowSize * 2,
|
||||
glowSize * 1.4,
|
||||
screenX - docWidth / 2 - glowPadding,
|
||||
screenY - docHeight / 2 - glowPadding,
|
||||
docWidth + glowPadding * 2,
|
||||
docHeight + glowPadding * 2,
|
||||
15,
|
||||
)
|
||||
} else {
|
||||
// Hexagonal glow for memory nodes
|
||||
const glowRadius = glowSize
|
||||
const glowRadius = nodeSize * 0.7
|
||||
const sides = 6
|
||||
for (let i = 0; i < sides; i++) {
|
||||
const angle = (i * 2 * Math.PI) / sides - Math.PI / 2
|
||||
|
|
|
|||
237
packages/memory-graph/src/utils/document-icons.ts
Normal file
237
packages/memory-graph/src/utils/document-icons.ts
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/**
|
||||
* Canvas-based document type icon rendering utilities
|
||||
* Simplified to match supported file types: PDF, TXT, MD, DOCX, DOC, RTF, CSV, JSON
|
||||
*/
|
||||
|
||||
export type DocumentIconType =
|
||||
| "text"
|
||||
| "pdf"
|
||||
| "md"
|
||||
| "markdown"
|
||||
| "docx"
|
||||
| "doc"
|
||||
| "rtf"
|
||||
| "csv"
|
||||
| "json"
|
||||
|
||||
/**
|
||||
* Draws a document type icon on canvas
|
||||
* @param ctx - Canvas 2D rendering context
|
||||
* @param x - X position (center of icon)
|
||||
* @param y - Y position (center of icon)
|
||||
* @param size - Icon size (width/height)
|
||||
* @param type - Document type
|
||||
* @param color - Icon color (default: white)
|
||||
*/
|
||||
export function drawDocumentIcon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
type: string,
|
||||
color = "rgba(255, 255, 255, 0.9)",
|
||||
): void {
|
||||
ctx.save()
|
||||
ctx.fillStyle = color
|
||||
ctx.strokeStyle = color
|
||||
ctx.lineWidth = Math.max(1, size / 12)
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineJoin = "round"
|
||||
|
||||
switch (type) {
|
||||
case "pdf":
|
||||
drawPdfIcon(ctx, x, y, size)
|
||||
break
|
||||
case "md":
|
||||
case "markdown":
|
||||
drawMarkdownIcon(ctx, x, y, size)
|
||||
break
|
||||
case "doc":
|
||||
case "docx":
|
||||
drawWordIcon(ctx, x, y, size)
|
||||
break
|
||||
case "rtf":
|
||||
drawRtfIcon(ctx, x, y, size)
|
||||
break
|
||||
case "csv":
|
||||
drawCsvIcon(ctx, x, y, size)
|
||||
break
|
||||
case "json":
|
||||
drawJsonIcon(ctx, x, y, size)
|
||||
break
|
||||
case "txt":
|
||||
case "text":
|
||||
default:
|
||||
drawTextIcon(ctx, x, y, size)
|
||||
break
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
// Individual icon drawing functions
|
||||
|
||||
function drawTextIcon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
): void {
|
||||
// Simple document outline with lines
|
||||
const w = size * 0.7
|
||||
const h = size * 0.85
|
||||
const cornerFold = size * 0.2
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x - w / 2, y - h / 2)
|
||||
ctx.lineTo(x + w / 2 - cornerFold, y - h / 2)
|
||||
ctx.lineTo(x + w / 2, y - h / 2 + cornerFold)
|
||||
ctx.lineTo(x + w / 2, y + h / 2)
|
||||
ctx.lineTo(x - w / 2, y + h / 2)
|
||||
ctx.closePath()
|
||||
ctx.stroke()
|
||||
|
||||
// Text lines
|
||||
const lineSpacing = size * 0.15
|
||||
const lineWidth = size * 0.4
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x - lineWidth / 2, y - lineSpacing)
|
||||
ctx.lineTo(x + lineWidth / 2, y - lineSpacing)
|
||||
ctx.moveTo(x - lineWidth / 2, y)
|
||||
ctx.lineTo(x + lineWidth / 2, y)
|
||||
ctx.moveTo(x - lineWidth / 2, y + lineSpacing)
|
||||
ctx.lineTo(x + lineWidth / 2, y + lineSpacing)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function drawPdfIcon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
): void {
|
||||
// Document with "PDF" text
|
||||
const w = size * 0.7
|
||||
const h = size * 0.85
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.rect(x - w / 2, y - h / 2, w, h)
|
||||
ctx.stroke()
|
||||
|
||||
// "PDF" letters (simplified)
|
||||
ctx.font = `bold ${size * 0.35}px sans-serif`
|
||||
ctx.textAlign = "center"
|
||||
ctx.textBaseline = "middle"
|
||||
ctx.fillText("PDF", x, y)
|
||||
}
|
||||
|
||||
function drawMarkdownIcon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
): void {
|
||||
// Document with "MD" text
|
||||
const w = size * 0.7
|
||||
const h = size * 0.85
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.rect(x - w / 2, y - h / 2, w, h)
|
||||
ctx.stroke()
|
||||
|
||||
// "MD" letters
|
||||
ctx.font = `bold ${size * 0.3}px sans-serif`
|
||||
ctx.textAlign = "center"
|
||||
ctx.textBaseline = "middle"
|
||||
ctx.fillText("MD", x, y)
|
||||
}
|
||||
|
||||
function drawWordIcon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
): void {
|
||||
// Document with "DOC" text
|
||||
const w = size * 0.7
|
||||
const h = size * 0.85
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.rect(x - w / 2, y - h / 2, w, h)
|
||||
ctx.stroke()
|
||||
|
||||
// "DOC" letters
|
||||
ctx.font = `bold ${size * 0.28}px sans-serif`
|
||||
ctx.textAlign = "center"
|
||||
ctx.textBaseline = "middle"
|
||||
ctx.fillText("DOC", x, y)
|
||||
}
|
||||
|
||||
function drawRtfIcon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
): void {
|
||||
// Document with "RTF" text
|
||||
const w = size * 0.7
|
||||
const h = size * 0.85
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.rect(x - w / 2, y - h / 2, w, h)
|
||||
ctx.stroke()
|
||||
|
||||
// "RTF" letters
|
||||
ctx.font = `bold ${size * 0.3}px sans-serif`
|
||||
ctx.textAlign = "center"
|
||||
ctx.textBaseline = "middle"
|
||||
ctx.fillText("RTF", x, y)
|
||||
}
|
||||
|
||||
function drawCsvIcon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
): void {
|
||||
// Grid table for CSV
|
||||
const w = size * 0.7
|
||||
const h = size * 0.85
|
||||
|
||||
ctx.strokeRect(x - w / 2, y - h / 2, w, h)
|
||||
|
||||
// Grid lines (2x2)
|
||||
ctx.beginPath()
|
||||
// Vertical line
|
||||
ctx.moveTo(x, y - h / 2)
|
||||
ctx.lineTo(x, y + h / 2)
|
||||
// Horizontal line
|
||||
ctx.moveTo(x - w / 2, y)
|
||||
ctx.lineTo(x + w / 2, y)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function drawJsonIcon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
): void {
|
||||
// Curly braces for JSON
|
||||
const w = size * 0.6
|
||||
const h = size * 0.8
|
||||
|
||||
// Left brace
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x - w / 4, y - h / 2)
|
||||
ctx.quadraticCurveTo(x - w / 2, y - h / 3, x - w / 2, y)
|
||||
ctx.quadraticCurveTo(x - w / 2, y + h / 3, x - w / 4, y + h / 2)
|
||||
ctx.stroke()
|
||||
|
||||
// Right brace
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x + w / 4, y - h / 2)
|
||||
ctx.quadraticCurveTo(x + w / 2, y - h / 3, x + w / 2, y)
|
||||
ctx.quadraticCurveTo(x + w / 2, y + h / 3, x + w / 4, y + h / 2)
|
||||
ctx.stroke()
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue