diff options
Diffstat (limited to 'packages/ui/memory-graph/graph-webgl-canvas.tsx')
| -rw-r--r-- | packages/ui/memory-graph/graph-webgl-canvas.tsx | 794 |
1 files changed, 0 insertions, 794 deletions
diff --git a/packages/ui/memory-graph/graph-webgl-canvas.tsx b/packages/ui/memory-graph/graph-webgl-canvas.tsx deleted file mode 100644 index af13eefc..00000000 --- a/packages/ui/memory-graph/graph-webgl-canvas.tsx +++ /dev/null @@ -1,794 +0,0 @@ -"use client"; - -import { Application, extend } from "@pixi/react"; -import { Container as PixiContainer, Graphics as PixiGraphics } from "pixi.js"; -import { memo, useCallback, useEffect, useMemo, useRef } from "react"; -import { colors } from "./constants"; -import type { GraphCanvasProps, MemoryEntry } from "./types"; - -// Register Pixi Graphics and Container so they can be used as JSX elements -extend({ Graphics: PixiGraphics, Container: PixiContainer }); - -export const GraphWebGLCanvas = memo<GraphCanvasProps>( - ({ - nodes, - edges, - panX, - panY, - zoom, - width, - height, - onNodeHover, - onNodeClick, - onNodeDragStart, - onNodeDragMove, - onNodeDragEnd, - onPanStart, - onPanMove, - onPanEnd, - onWheel, - onDoubleClick, - onTouchStart, - onTouchMove, - onTouchEnd, - draggingNodeId, - }) => { - const containerRef = useRef<HTMLDivElement>(null); - const isPanningRef = useRef(false); - const currentHoveredRef = useRef<string | null>(null); - const pointerDownPosRef = useRef<{ x: number; y: number } | null>(null); - const pointerMovedRef = useRef(false); - // World container that is transformed instead of redrawing every pan/zoom - const worldContainerRef = useRef<PixiContainer | null>(null); - - // Throttled wheel handling ------------------------------------------- - const pendingWheelDeltaRef = useRef<{ dx: number; dy: number }>({ - dx: 0, - dy: 0, - }); - const wheelRafRef = useRef<number | null>(null); - // Removed bitmap caching due to black-screen issues – throttle already boosts zoom performance - - // Persistent graphics refs - const gridG = useRef<PixiGraphics | null>(null); - const edgesG = useRef<PixiGraphics | null>(null); - const docsG = useRef<PixiGraphics | null>(null); - const memsG = useRef<PixiGraphics | null>(null); - - // ---------- Zoom bucket (reduces redraw frequency) ---------- - const zoomBucket = useMemo(() => Math.round(zoom * 4) / 4, [zoom]); - - // Redraw layers only when their data changes ---------------------- - useEffect(() => { - if (gridG.current) drawGrid(gridG.current); - }, [panX, panY, zoom, width, height]); - - useEffect(() => { - if (edgesG.current) drawEdges(edgesG.current); - }, [edgesG.current, edges, nodes, zoomBucket]); - - useEffect(() => { - if (docsG.current) drawDocuments(docsG.current); - }, [docsG.current, nodes, zoomBucket]); - - useEffect(() => { - if (memsG.current) drawMemories(memsG.current); - }, [memsG.current, nodes, zoomBucket]); - - // Apply pan & zoom via world transform instead of geometry rebuilds - useEffect(() => { - if (worldContainerRef.current) { - worldContainerRef.current.position.set(panX, panY); - worldContainerRef.current.scale.set(zoom); - } - }, [panX, panY, zoom]); - - // No bitmap caching – nothing to clean up - - /* ---------- Helpers ---------- */ - const getNodeAtPosition = useCallback( - (clientX: number, clientY: number): string | null => { - const rect = containerRef.current?.getBoundingClientRect(); - if (!rect) return null; - - const localX = clientX - rect.left; - const localY = clientY - rect.top; - - const worldX = (localX - panX) / zoom; - const worldY = (localY - panY) / zoom; - - for (const node of nodes) { - if (node.type === "document") { - const halfW = (node.size * 1.4) / 2; - const halfH = (node.size * 0.9) / 2; - if ( - worldX >= node.x - halfW && - worldX <= node.x + halfW && - worldY >= node.y - halfH && - worldY <= node.y + halfH - ) { - return node.id; - } - } else if (node.type === "memory") { - const r = node.size / 2; - const dx = worldX - node.x; - const dy = worldY - node.y; - if (dx * dx + dy * dy <= r * r) { - return node.id; - } - } - } - return null; - }, - [nodes, panX, panY, zoom], - ); - - /* ---------- Grid drawing ---------- */ - const drawGrid = useCallback( - (g: PixiGraphics) => { - g.clear(); - - const gridColor = 0x94a3b8; // rgb(148,163,184) - const gridAlpha = 0.03; - const gridSpacing = 100 * zoom; - - // panning offsets - const offsetX = panX % gridSpacing; - const offsetY = panY % gridSpacing; - - g.lineStyle(1, gridColor, gridAlpha); - - // vertical lines - for (let x = offsetX; x < width; x += gridSpacing) { - g.moveTo(x, 0); - g.lineTo(x, height); - } - - // horizontal lines - for (let y = offsetY; y < height; y += gridSpacing) { - g.moveTo(0, y); - g.lineTo(width, y); - } - - // Stroke to render grid lines - g.stroke(); - }, - [panX, panY, zoom, width, height], - ); - - /* ---------- Color parsing ---------- */ - const toHexAlpha = (input: string): { hex: number; alpha: number } => { - if (!input) return { hex: 0xffffff, alpha: 1 }; - const str = input.trim().toLowerCase(); - // rgba() or rgb() - const rgbaMatch = str - .replace(/\s+/g, "") - .match(/rgba?\((\d+),(\d+),(\d+)(?:,(\d*\.?\d+))?\)/i); - if (rgbaMatch) { - const r = Number.parseInt(rgbaMatch[1] || "0"); - const g = Number.parseInt(rgbaMatch[2] || "0"); - const b = Number.parseInt(rgbaMatch[3] || "0"); - const a = - rgbaMatch[4] !== undefined ? Number.parseFloat(rgbaMatch[4]) : 1; - return { hex: (r << 16) + (g << 8) + b, alpha: a }; - } - // #rrggbb or #rrggbbaa - if (str.startsWith("#")) { - const hexBody = str.slice(1); - if (hexBody.length === 6) { - return { hex: Number.parseInt(hexBody, 16), alpha: 1 }; - } - if (hexBody.length === 8) { - const rgb = Number.parseInt(hexBody.slice(0, 6), 16); - const aByte = Number.parseInt(hexBody.slice(6, 8), 16); - return { hex: rgb, alpha: aByte / 255 }; - } - } - // 0xRRGGBB - if (str.startsWith("0x")) { - return { hex: Number.parseInt(str, 16), alpha: 1 }; - } - return { hex: 0xffffff, alpha: 1 }; - }; - - const drawDocuments = useCallback( - (g: PixiGraphics) => { - g.clear(); - - nodes.forEach((node) => { - if (node.type !== "document") return; - - // World-space coordinates – container transform handles pan/zoom - const screenX = node.x; - const screenY = node.y; - const nodeSize = node.size; - - const docWidth = nodeSize * 1.4; - const docHeight = nodeSize * 0.9; - - // Choose colors similar to canvas version - const fill = node.isDragging - ? colors.document.accent - : node.isHovered - ? colors.document.secondary - : colors.document.primary; - - const strokeCol = node.isDragging - ? colors.document.glow - : node.isHovered - ? colors.document.accent - : colors.document.border; - - const { hex: fillHex, alpha: fillAlpha } = toHexAlpha(fill); - const { hex: strokeHex, alpha: strokeAlpha } = toHexAlpha(strokeCol); - - // Stroke first then fill for proper shape borders - const docStrokeWidth = - (node.isDragging ? 3 : node.isHovered ? 2 : 1) / zoom; - g.lineStyle(docStrokeWidth, strokeHex, strokeAlpha); - g.beginFill(fillHex, fillAlpha); - - const radius = zoom < 0.3 ? 6 : 12; - g.drawRoundedRect( - screenX - docWidth / 2, - screenY - docHeight / 2, - docWidth, - docHeight, - radius, - ); - g.endFill(); - - // Inner highlight for glass effect (match GraphCanvas) - if (zoom >= 0.3 && (node.isHovered || node.isDragging)) { - const { hex: hlHex } = toHexAlpha("#ffffff"); - // Inner highlight stroke width constant - const innerStroke = 1 / zoom; - g.lineStyle(innerStroke, hlHex, 0.1); - g.drawRoundedRect( - screenX - docWidth / 2 + 1, - screenY - docHeight / 2 + 1, - docWidth - 2, - docHeight - 2, - radius - 1, - ); - g.stroke(); - } - }); - }, - [nodes, zoom], - ); - - /* ---------- Memories layer ---------- */ - const drawMemories = useCallback( - (g: PixiGraphics) => { - g.clear(); - - nodes.forEach((node) => { - if (node.type !== "memory") return; - - const mem = node.data as MemoryEntry; - const screenX = node.x; - const screenY = node.y; - const nodeSize = node.size; - - const radius = nodeSize / 2; - - // status checks - const isForgotten = - mem?.isForgotten || - (mem?.forgetAfter && - new Date(mem.forgetAfter).getTime() < Date.now()); - const isLatest = mem?.isLatest; - const expiringSoon = - mem?.forgetAfter && - !isForgotten && - new Date(mem.forgetAfter).getTime() - Date.now() < - 1000 * 60 * 60 * 24 * 7; - const isNew = - !isForgotten && - new Date(mem?.createdAt).getTime() > - Date.now() - 1000 * 60 * 60 * 24; - - // colours - let fillColor = colors.memory.primary; - let borderColor = colors.memory.border; - let glowColor = colors.memory.glow; - - if (isForgotten) { - fillColor = colors.status.forgotten; - borderColor = "rgba(220,38,38,0.3)"; - glowColor = "rgba(220,38,38,0.2)"; - } else if (expiringSoon) { - borderColor = colors.status.expiring; - glowColor = colors.accent.amber; - } else if (isNew) { - borderColor = colors.status.new; - glowColor = colors.accent.emerald; - } - - if (node.isDragging) { - fillColor = colors.memory.accent; - borderColor = glowColor; - } else if (node.isHovered) { - fillColor = colors.memory.secondary; - } - - const { hex: fillHex, alpha: fillAlpha } = toHexAlpha(fillColor); - const { hex: borderHex, alpha: borderAlpha } = - toHexAlpha(borderColor); - - // Match canvas behavior: multiply by isLatest global alpha - const globalAlpha = isLatest ? 1 : 0.4; - const finalFillAlpha = globalAlpha * fillAlpha; - const finalStrokeAlpha = globalAlpha * borderAlpha; - // Stroke first then fill for visible border - const memStrokeW = - (node.isDragging ? 3 : node.isHovered ? 2 : 1.5) / zoom; - g.lineStyle(memStrokeW, borderHex, finalStrokeAlpha); - g.beginFill(fillHex, finalFillAlpha); - - if (zoom < 0.3) { - // simplified circle when zoomed out - g.drawCircle(screenX, screenY, radius); - } else { - // hexagon - const sides = 6; - const points: number[] = []; - for (let i = 0; i < sides; i++) { - const angle = (i * 2 * Math.PI) / sides - Math.PI / 2; - points.push(screenX + radius * Math.cos(angle)); - points.push(screenY + radius * Math.sin(angle)); - } - g.drawPolygon(points); - } - - g.endFill(); - - // Status overlays (forgotten / new) – match GraphCanvas visuals - if (isForgotten) { - const { hex: crossHex, alpha: crossAlpha } = toHexAlpha( - "rgba(220,38,38,0.4)", - ); - // Cross/ dot overlay stroke widths constant - const overlayStroke = 2 / zoom; - g.lineStyle(overlayStroke, crossHex, globalAlpha * crossAlpha); - const rCross = nodeSize * 0.25; - g.moveTo(screenX - rCross, screenY - rCross); - g.lineTo(screenX + rCross, screenY + rCross); - g.moveTo(screenX + rCross, screenY - rCross); - g.lineTo(screenX - rCross, screenY + rCross); - g.stroke(); - } else if (isNew) { - const { hex: dotHex, alpha: dotAlpha } = toHexAlpha( - colors.status.new, - ); - // Dot scales with node (GraphCanvas behaviour) - const dotRadius = Math.max(2, nodeSize * 0.15); - g.beginFill(dotHex, globalAlpha * dotAlpha); - g.drawCircle( - screenX + nodeSize * 0.25, - screenY - nodeSize * 0.25, - dotRadius, - ); - g.endFill(); - } - }); - }, - [nodes, zoom], - ); - - /* ---------- Edges layer ---------- */ - // Helper: draw dashed quadratic curve to approximate canvas setLineDash - const drawDashedQuadratic = useCallback( - ( - g: PixiGraphics, - sx: number, - sy: number, - cx: number, - cy: number, - tx: number, - ty: number, - dash = 10, - gap = 5, - ) => { - // Sample the curve and accumulate lines per dash to avoid overdraw - const curveLength = Math.sqrt((sx - tx) ** 2 + (sy - ty) ** 2); - const totalSamples = Math.max( - 20, - Math.min(120, Math.floor(curveLength / 10)), - ); - let prevX = sx; - let prevY = sy; - let distanceSinceToggle = 0; - let drawSegment = true; - let hasActiveDash = false; - let dashStartX = sx; - let dashStartY = sy; - - for (let i = 1; i <= totalSamples; i++) { - const t = i / totalSamples; - const mt = 1 - t; - const x = mt * mt * sx + 2 * mt * t * cx + t * t * tx; - const y = mt * mt * sy + 2 * mt * t * cy + t * t * ty; - - const dx = x - prevX; - const dy = y - prevY; - const segLen = Math.sqrt(dx * dx + dy * dy); - distanceSinceToggle += segLen; - - if (drawSegment) { - if (!hasActiveDash) { - dashStartX = prevX; - dashStartY = prevY; - hasActiveDash = true; - } - } - - const threshold = drawSegment ? dash : gap; - if (distanceSinceToggle >= threshold) { - // end of current phase - if (drawSegment && hasActiveDash) { - g.moveTo(dashStartX, dashStartY); - g.lineTo(prevX, prevY); - g.stroke(); - hasActiveDash = false; - } - distanceSinceToggle = 0; - drawSegment = !drawSegment; - // If we transition into draw mode, start a new dash at current segment start - if (drawSegment) { - dashStartX = prevX; - dashStartY = prevY; - hasActiveDash = true; - } - } - - prevX = x; - prevY = y; - } - - // Flush any active dash at the end - if (drawSegment && hasActiveDash) { - g.moveTo(dashStartX, dashStartY); - g.lineTo(prevX, prevY); - g.stroke(); - } - }, - [], - ); - const drawEdges = useCallback( - (g: PixiGraphics) => { - g.clear(); - - // Match GraphCanvas LOD behaviour - const useSimplified = zoom < 0.3; - - // quick node lookup - const nodeMap = new Map(nodes.map((n) => [n.id, n])); - - edges.forEach((edge) => { - // Skip very weak doc-memory edges when zoomed out – behaviour copied from GraphCanvas - if ( - useSimplified && - edge.edgeType === "doc-memory" && - (edge.visualProps?.opacity ?? 1) < 0.3 - ) { - return; - } - const source = nodeMap.get(edge.source); - const target = nodeMap.get(edge.target); - if (!source || !target) return; - - const sx = source.x; - const sy = source.y; - const tx = target.x; - const ty = target.y; - - // No viewport culling here because container transform handles visibility - - let lineWidth = Math.max(1, edge.visualProps?.thickness ?? 1); - // Use opacity exactly as provided to match GraphCanvas behaviour - let opacity = edge.visualProps.opacity; - let col = edge.color || colors.connection.weak; - - if (edge.edgeType === "doc-memory") { - lineWidth = 1; - opacity = 0.9; - col = colors.connection.memory; - - if (useSimplified && opacity < 0.3) return; - } else if (edge.edgeType === "doc-doc") { - opacity = Math.max(0, edge.similarity * 0.5); - lineWidth = Math.max(1, edge.similarity * 2); - col = colors.connection.medium; - if (edge.similarity > 0.85) col = colors.connection.strong; - } else if (edge.edgeType === "version") { - col = edge.color || colors.relations.updates; - opacity = 0.8; - lineWidth = 2; - } - - const { hex: strokeHex, alpha: colorAlpha } = toHexAlpha(col); - const finalEdgeAlpha = Math.max(0, Math.min(1, opacity * colorAlpha)); - - // Always use round line caps (same as Canvas 2D) - const screenLineWidth = lineWidth / zoom; - g.lineStyle(screenLineWidth, strokeHex, finalEdgeAlpha); - - if (edge.edgeType === "version") { - // double line effect to match canvas (outer thicker, faint + inner thin) - g.lineStyle(3 / zoom, strokeHex, finalEdgeAlpha * 0.3); - g.moveTo(sx, sy); - g.lineTo(tx, ty); - g.stroke(); - - g.lineStyle(1 / zoom, strokeHex, finalEdgeAlpha); - g.moveTo(sx, sy); - g.lineTo(tx, ty); - g.stroke(); - - // arrow head - const angle = Math.atan2(ty - sy, tx - sx); - const arrowLen = Math.max(6 / zoom, 8); - const nodeRadius = target.size / 2; - const ax = tx - Math.cos(angle) * (nodeRadius + 2); - const ay = ty - Math.sin(angle) * (nodeRadius + 2); - - g.moveTo(ax, ay); - g.lineTo( - ax - arrowLen * Math.cos(angle - Math.PI / 6), - ay - arrowLen * Math.sin(angle - Math.PI / 6), - ); - g.moveTo(ax, ay); - g.lineTo( - ax - arrowLen * Math.cos(angle + Math.PI / 6), - ay - arrowLen * Math.sin(angle + Math.PI / 6), - ); - g.stroke(); - } else { - // straight line when zoomed out; dashed curved when zoomed in for doc-doc - if (useSimplified) { - g.moveTo(sx, sy); - g.lineTo(tx, ty); - g.stroke(); - } else { - const midX = (sx + tx) / 2; - const midY = (sy + ty) / 2; - const dx = tx - sx; - const dy = ty - sy; - const dist = Math.sqrt(dx * dx + dy * dy); - const ctrlOffset = - edge.edgeType === "doc-memory" ? 15 : Math.min(30, dist * 0.2); - - const cx = midX + ctrlOffset * (dy / dist); - const cy = midY - ctrlOffset * (dx / dist); - - if (edge.edgeType === "doc-doc") { - if (useSimplified) { - // Straight line when zoomed out (no dash) - g.moveTo(sx, sy); - g.quadraticCurveTo(cx, cy, tx, ty); - g.stroke(); - } else { - // Dash lengths scale with zoom to keep screen size constant - const dash = 10 / zoom; - const gap = 5 / zoom; - drawDashedQuadratic(g, sx, sy, cx, cy, tx, ty, dash, gap); - } - } else { - g.moveTo(sx, sy); - g.quadraticCurveTo(cx, cy, tx, ty); - g.stroke(); - } - } - } - }); - }, - [edges, nodes, zoom, width, drawDashedQuadratic], - ); - - /* ---------- pointer handlers (unchanged) ---------- */ - // Pointer move (pan or drag) - const handlePointerMove = useCallback( - (e: React.PointerEvent<HTMLDivElement>) => { - const mouseEvent = { - clientX: e.clientX, - clientY: e.clientY, - preventDefault: () => {}, - stopPropagation: () => {}, - } as React.MouseEvent; - - if (draggingNodeId) { - // Node dragging handled elsewhere (future steps) - onNodeDragMove(mouseEvent); - } else if (isPanningRef.current) { - onPanMove(mouseEvent); - } - - // Track movement for distinguishing click vs drag/pan - if (pointerDownPosRef.current) { - const dx = e.clientX - pointerDownPosRef.current.x; - const dy = e.clientY - pointerDownPosRef.current.y; - if (Math.sqrt(dx * dx + dy * dy) > 3) pointerMovedRef.current = true; - } - - // Hover detection - const nodeId = getNodeAtPosition(e.clientX, e.clientY); - if (nodeId !== currentHoveredRef.current) { - currentHoveredRef.current = nodeId; - onNodeHover(nodeId); - } - }, - [ - draggingNodeId, - onNodeDragMove, - onPanMove, - onNodeHover, - getNodeAtPosition, - ], - ); - - const handlePointerDown = useCallback( - (e: React.PointerEvent<HTMLDivElement>) => { - const mouseEvent = { - clientX: e.clientX, - clientY: e.clientY, - preventDefault: () => {}, - stopPropagation: () => {}, - } as React.MouseEvent; - - const nodeId = getNodeAtPosition(e.clientX, e.clientY); - if (nodeId) { - onNodeDragStart(nodeId, mouseEvent); - // drag handled externally - } else { - onPanStart(mouseEvent); - isPanningRef.current = true; - } - pointerDownPosRef.current = { x: e.clientX, y: e.clientY }; - pointerMovedRef.current = false; - }, - [onPanStart, onNodeDragStart, getNodeAtPosition], - ); - - const handlePointerUp = useCallback( - (e: React.PointerEvent<HTMLDivElement>) => { - const wasPanning = isPanningRef.current; - if (draggingNodeId) onNodeDragEnd(); - else if (wasPanning) onPanEnd(); - - // Consider it a click if not panning and movement was minimal - if (!wasPanning && !pointerMovedRef.current) { - const nodeId = getNodeAtPosition(e.clientX, e.clientY); - if (nodeId) onNodeClick(nodeId); - } - - isPanningRef.current = false; - pointerDownPosRef.current = null; - pointerMovedRef.current = false; - }, - [draggingNodeId, onNodeDragEnd, onPanEnd, getNodeAtPosition, onNodeClick], - ); - - // Click handler – opens detail panel - const handleClick = useCallback( - (e: React.MouseEvent<HTMLDivElement>) => { - if (isPanningRef.current) return; - const nodeId = getNodeAtPosition(e.clientX, e.clientY); - if (nodeId) onNodeClick(nodeId); - }, - [getNodeAtPosition, onNodeClick], - ); - - // Click handled in pointer up to avoid duplicate events - - const handleWheel = useCallback( - (e: React.WheelEvent<HTMLDivElement>) => { - e.preventDefault(); - e.stopPropagation(); - - // Accumulate deltas - pendingWheelDeltaRef.current.dx += e.deltaX; - pendingWheelDeltaRef.current.dy += e.deltaY; - - // Schedule a single update per frame - if (wheelRafRef.current === null) { - wheelRafRef.current = requestAnimationFrame(() => { - const { dx, dy } = pendingWheelDeltaRef.current; - pendingWheelDeltaRef.current = { dx: 0, dy: 0 }; - - // @ts-expect-error - onWheel({ - deltaY: dy, - deltaX: dx, - clientX: e.clientX, - clientY: e.clientY, - currentTarget: containerRef.current, - nativeEvent: e.nativeEvent, - preventDefault: () => {}, - stopPropagation: () => {}, - } as React.WheelEvent); - - wheelRafRef.current = null; - - // nothing else – caching removed - }); - } - }, - [onWheel], - ); - - // Cleanup any pending RAF on unmount - useEffect(() => { - return () => { - if (wheelRafRef.current !== null) { - cancelAnimationFrame(wheelRafRef.current); - } - }; - }, []); - - return ( - <div - className="absolute inset-0" - onDoubleClick={(ev) => - onDoubleClick?.(ev as unknown as React.MouseEvent) - } - onKeyDown={(ev) => { - if (ev.key === "Enter") - handleClick(ev as unknown as React.MouseEvent<HTMLDivElement>); - }} - onPointerDown={handlePointerDown} - onPointerLeave={() => { - if (draggingNodeId) onNodeDragEnd(); - if (isPanningRef.current) onPanEnd(); - isPanningRef.current = false; - pointerDownPosRef.current = null; - pointerMovedRef.current = false; - }} - onPointerMove={handlePointerMove} - onPointerUp={handlePointerUp} - onTouchStart={onTouchStart} - onTouchMove={onTouchMove} - onTouchEnd={onTouchEnd} - onWheel={handleWheel} - ref={containerRef} - role="application" - style={{ - cursor: draggingNodeId ? "grabbing" : "move", - touchAction: "none", - userSelect: "none", - WebkitUserSelect: "none", - }} - > - <Application - preference="webgl" - antialias - autoDensity - backgroundColor={0x0f1419} - height={height} - resolution={ - typeof window !== "undefined" ? window.devicePixelRatio : 1 - } - width={width} - > - {/* Grid background (not affected by world transform) */} - <pixiGraphics ref={gridG} draw={() => {}} /> - - {/* World container that pans/zooms as a single transform */} - <pixiContainer ref={worldContainerRef}> - {/* Edges */} - <pixiGraphics ref={edgesG} draw={() => {}} /> - - {/* Documents */} - <pixiGraphics ref={docsG} draw={() => {}} /> - - {/* Memories */} - <pixiGraphics ref={memsG} draw={() => {}} /> - </pixiContainer> - </Application> - </div> - ); - }, -); - -GraphWebGLCanvas.displayName = "GraphWebGLCanvas"; |