Files
qr-code-generator/src/components/PreviewPanel.tsx
T
2025-11-13 16:30:46 +08:00

125 lines
4.2 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
import { renderQRToCanvas } from '../lib/qrGenerator';
import { QRCodeInstance, QRConfig, QRInfo } from '../types/qr';
import './PreviewPanel.css';
interface PreviewPanelProps {
qr: QRCodeInstance | null;
config: QRConfig;
info: QRInfo | null;
onExportPNG: () => void;
onExportSVG: () => void;
onExportConfig: () => void;
}
export function PreviewPanel({ qr, config, info, onExportPNG, onExportSVG, onExportConfig }: PreviewPanelProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
if (!qr || !canvasRef.current) return;
setIsAnimating(true);
const timer = setTimeout(() => setIsAnimating(false), 500);
try {
const canvas = renderQRToCanvas(qr, config, 512);
const ctx = canvasRef.current.getContext('2d');
if (ctx) {
canvasRef.current.width = 512;
canvasRef.current.height = 512;
ctx.drawImage(canvas, 0, 0);
}
} catch (error) {
console.error('Error rendering QR code:', error);
}
return () => clearTimeout(timer);
}, [qr, config]);
if (!qr || !info) {
return (
<div className="preview-panel glass-card fade-in">
<h2 className="panel-title">Preview</h2>
<div className="preview-empty">
<div className="empty-icon">📱</div>
<p className="empty-text">Enter content to generate QR code</p>
</div>
</div>
);
}
return (
<div className="preview-panel glass-card fade-in">
<h2 className="panel-title">Preview</h2>
<div className={`preview-container ${isAnimating ? 'animating' : ''}`}>
<canvas ref={canvasRef} className="preview-canvas" />
</div>
{/* QR Info */}
<div className="info-section">
<h3 className="info-title">Technical Details</h3>
<div className="info-grid">
<div className="info-item">
<span className="info-label">Version</span>
<span className="info-value">{info.version}</span>
</div>
<div className="info-item">
<span className="info-label">Error Correction</span>
<span className="info-value">{info.errorCorrectionLevel}</span>
</div>
<div className="info-item">
<span className="info-label">Mask Pattern</span>
<span className="info-value">{info.maskPattern === -1 ? 'Auto' : info.maskPattern}</span>
</div>
<div className="info-item">
<span className="info-label">Module Style</span>
<span className="info-value">{config.moduleStyle}</span>
</div>
</div>
<div className="capacity-section">
<div className="capacity-header">
<span className="capacity-label">Data Capacity Usage</span>
<span className="capacity-value">{info.capacityPercentage.toFixed(1)}%</span>
</div>
<div className="capacity-bar">
<div
className="capacity-fill"
style={{
width: `${Math.min(info.capacityPercentage, 100)}%`,
background: info.capacityPercentage > 90
? 'linear-gradient(90deg, #f56565, #e53e3e)'
: 'linear-gradient(90deg, var(--accent-primary), var(--accent-secondary))'
}}
/>
</div>
<div className="capacity-details">
<span>{info.usedCapacity} / {info.capacity} bytes</span>
</div>
</div>
</div>
{/* Export Actions */}
<div className="export-section">
<h3 className="info-title">Export</h3>
<div className="export-buttons">
<button onClick={onExportPNG} className="export-button">
<span className="button-icon">🖼️</span>
<span>PNG</span>
</button>
<button onClick={onExportSVG} className="export-button">
<span className="button-icon">📐</span>
<span>SVG</span>
</button>
<button onClick={onExportConfig} className="export-button button-secondary">
<span className="button-icon">⚙️</span>
<span>Config</span>
</button>
</div>
</div>
</div>
);
}