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:
Vidya Rupak 2025-12-21 18:25:42 -07:00
parent ef59792f7b
commit 72df14c7f7
3 changed files with 312 additions and 12 deletions

View file

@ -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

View file

@ -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

View 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()
}