import { useEffect, useRef } from 'react'; import { Box } from '@mui/material'; import { valueToColor } from '../utils/colorUtils'; interface MultiplicationTableProps { prime: number; onLoadingChange?: (isLoading: boolean) => void; showColorBar: boolean; } export function MultiplicationTable({ prime, onLoadingChange, showColorBar }: MultiplicationTableProps) { const canvasRef = useRef(null); const colorBarRef = useRef(null); const containerRef = useRef(null); const workerRef = useRef(null); // Effect for computing multiplication table using Web Worker useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; // Terminate existing worker if any (this handles interruption) if (workerRef.current) { workerRef.current.terminate(); } // Set loading state onLoadingChange?.(true); // Create new worker const worker = new Worker(new URL('../workers/multiplicationWorker.ts', import.meta.url), { type: 'module' }); workerRef.current = worker; // Handle messages from worker worker.onmessage = (e: MessageEvent<{ type: string; imageData: ImageData }>) => { if (e.data.type === 'complete') { const { imageData } = e.data; // Set canvas dimensions to match grid size (1 pixel per cell) const gridSize = prime - 1; // Resize canvas only when we have the new image ready (to prevent flickering) if (canvas.width !== gridSize || canvas.height !== gridSize) { canvas.width = gridSize; canvas.height = gridSize; } // Create ImageBitmap from ImageData for efficient rendering createImageBitmap(imageData).then((bitmap) => { // Draw the bitmap at 1:1 scale (1 pixel per cell) ctx.imageSmoothingEnabled = false; // Disable smoothing for crisp pixels ctx.drawImage(bitmap, 0, 0); onLoadingChange?.(false); }); } }; // Send computation request to worker worker.postMessage({ prime }); // Cleanup function return () => { worker.terminate(); }; }, [prime]); // Effect for drawing color bar (remains synchronous as it's simple) useEffect(() => { if (!showColorBar) return; const colorBarCanvas = colorBarRef.current; if (!colorBarCanvas) return; const colorBarCtx = colorBarCanvas.getContext('2d'); if (!colorBarCtx) return; // Draw color bar const colorBarHeight = 400; const colorBarWidth = 20; colorBarCanvas.width = colorBarWidth; colorBarCanvas.height = colorBarHeight; // Draw gradient - use prime-1 as max value to match data range const steps = 256; // Use more steps for smoother gradient const stepHeight = colorBarHeight / steps; for (let i = 0; i < steps; i++) { // Map i to value in range [0, prime-1] correctly const value = Math.floor((i / (steps - 1)) * (prime - 1)); const color = valueToColor(value, prime - 1); colorBarCtx.fillStyle = color; colorBarCtx.fillRect(0, colorBarHeight - (i + 1) * stepHeight, colorBarWidth, stepHeight); } // Draw border around color bar colorBarCtx.strokeStyle = '#000'; colorBarCtx.strokeRect(0, 0, colorBarWidth, colorBarHeight); }, [prime, showColorBar]); return ( {showColorBar && ( {prime - 1} 0 )} ); }