Files
sitemente/components/mission-control/PDFViewerClient.tsx
T

254 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useRef, useEffect } from "react";
declare global {
interface Window {
pdfjsLib: any;
}
}
export default function PDFViewerClient() {
const [pdfData, setPdfData] = useState<string | null>(null);
const [pdfName, setPdfName] = useState<string>("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [numPages, setNumPages] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const [scale, setScale] = useState(1.5);
const [pdfjsLoaded, setPdfjsLoaded] = useState(false);
const [debugInfo, setDebugInfo] = useState<string>("");
const canvasRef = useRef<HTMLCanvasElement>(null);
const pdfDocRef = useRef<any>(null);
useEffect(() => {
// Load PDF.js from CDN
const script = document.createElement("script");
script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
script.async = true;
script.onload = () => {
try {
window.pdfjsLib = window.pdfjsLib;
window.pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
setPdfjsLoaded(true);
setDebugInfo("PDF.js loaded successfully");
} catch (e) {
setError("Failed to initialize PDF.js");
setDebugInfo("Error loading PDF.js: " + e);
}
};
script.onerror = () => {
setError("Failed to load PDF.js library");
setDebugInfo("Script failed to load");
};
document.body.appendChild(script);
}, []);
const renderPage = async (pageNum: number) => {
if (!pdfDocRef.current || !canvasRef.current || !window.pdfjsLib) {
setDebugInfo("Cannot render: pdfDoc=" + !!pdfDocRef.current + ", canvas=" + !!canvasRef.current + ", pdfjs=" + !!window.pdfjsLib);
return;
}
try {
setDebugInfo("Rendering page " + pageNum);
const page = await pdfDocRef.current.getPage(pageNum);
const viewport = page.getViewport({ scale });
const canvas = canvasRef.current;
const context = canvas.getContext("2d");
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({
canvasContext: context,
viewport: viewport,
}).promise;
setDebugInfo("Page " + pageNum + " rendered successfully");
} catch (err) {
setDebugInfo("Render error: " + err);
console.error("Error rendering page:", err);
}
};
useEffect(() => {
if (pdfDocRef.current && currentPage && pdfjsLoaded) {
renderPage(currentPage);
}
}, [currentPage, scale, pdfjsLoaded]);
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file || !window.pdfjsLib) {
setError("Please wait for PDF.js to load or check console");
return;
}
if (file.type !== "application/pdf") {
setError("Please select a PDF file");
return;
}
setLoading(true);
setError(null);
setPdfName(file.name);
setCurrentPage(1);
setDebugInfo("Loading file: " + file.name);
try {
const arrayBuffer = await file.arrayBuffer();
setDebugInfo("File loaded, parsing PDF...");
const pdf = await window.pdfjsLib.getDocument({ data: arrayBuffer }).promise;
pdfDocRef.current = pdf;
setNumPages(pdf.numPages);
setPdfData("local");
setDebugInfo("PDF parsed, " + pdf.numPages + " pages");
await renderPage(1);
} catch (err) {
setError("Failed to load PDF: " + err);
setDebugInfo("Error: " + err);
console.error(err);
}
setLoading(false);
};
const handleUrlSubmit = async () => {
const input = prompt("Enter PDF URL:");
if (!input || !window.pdfjsLib) return;
setLoading(true);
setError(null);
setCurrentPage(1);
try {
new URL(input);
setPdfName(input.split("/").pop() || "document.pdf");
const response = await fetch(input);
if (!response.ok) throw new Error("Failed to fetch PDF");
const arrayBuffer = await response.arrayBuffer();
const pdf = await window.pdfjsLib.getDocument({ data: arrayBuffer }).promise;
pdfDocRef.current = pdf;
setNumPages(pdf.numPages);
setPdfData("url");
await renderPage(1);
} catch (err) {
setError("Failed to load PDF from URL. Make sure the URL is publicly accessible.");
console.error(err);
}
setLoading(false);
};
const goToPrevPage = () => {
if (currentPage > 1) setCurrentPage(currentPage - 1);
};
const goToNextPage = () => {
if (currentPage < numPages) setCurrentPage(currentPage + 1);
};
return (
<div className="flex flex-col h-full">
{/* Toolbar */}
<div className="bg-slate-900/50 border-b border-slate-800 px-6 py-3 flex-shrink-0">
<div className="flex items-center gap-4 flex-wrap">
<label className="cursor-pointer bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors">
📁 Upload PDF
<input
type="file"
accept="application/pdf"
onChange={handleFileUpload}
className="hidden"
disabled={!pdfjsLoaded}
/>
</label>
<button
onClick={handleUrlSubmit}
disabled={!pdfjsLoaded}
className="bg-slate-700 hover:bg-slate-600 disabled:opacity-50 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
>
🔗 Load from URL
</button>
{!pdfjsLoaded && (
<span className="text-yellow-400 text-sm animate-pulse">Loading PDF.js...</span>
)}
{pdfData && (
<>
<div className="h-6 w-px bg-slate-600 mx-2" />
<button onClick={goToPrevPage} disabled={currentPage <= 1}
className="bg-slate-700 hover:bg-slate-600 disabled:opacity-50 text-white px-3 py-2 rounded-lg text-sm">
Prev
</button>
<span className="text-slate-300 text-sm">Page {currentPage} of {numPages}</span>
<button onClick={goToNextPage} disabled={currentPage >= numPages}
className="bg-slate-700 hover:bg-slate-600 disabled:opacity-50 text-white px-3 py-2 rounded-lg text-sm">
Next
</button>
<div className="h-6 w-px bg-slate-600 mx-2" />
<button onClick={() => setScale(Math.max(0.5, scale - 0.25))}
className="bg-slate-700 hover:bg-slate-600 text-white px-3 py-2 rounded-lg text-sm"></button>
<span className="text-slate-300 text-sm w-16 text-center">{Math.round(scale * 100)}%</span>
<button onClick={() => setScale(scale + 0.25)}
className="bg-slate-700 hover:bg-slate-600 text-white px-3 py-2 rounded-lg text-sm"></button>
</>
)}
</div>
</div>
{/* Debug info */}
{debugInfo && (
<div className="bg-slate-900 px-6 py-1 text-xs text-slate-500 border-b border-slate-800">
Debug: {debugInfo}
</div>
)}
{/* Error */}
{error && (
<div className="bg-red-900/50 border border-red-700 rounded-lg p-4 m-4 max-w-md">
<h3 className="text-red-400 font-bold mb-2">Error</h3>
<p className="text-red-300 text-sm">{error}</p>
<button onClick={() => setError(null)}
className="mt-2 bg-red-700 hover:bg-red-600 text-white px-4 py-1 rounded text-sm">
Dismiss
</button>
</div>
)}
{/* Content */}
<div className="flex-1 overflow-auto bg-slate-800 p-4">
{!pdfData && !loading && (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="text-8xl mb-6 opacity-50">📄</div>
<h2 className="text-xl font-bold text-white mb-2">No PDF Loaded</h2>
<p className="text-slate-400 mb-6">Upload a PDF or enter a URL</p>
</div>
</div>
)}
{loading && (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="animate-spin text-6xl mb-4"></div>
<p className="text-slate-400">Loading PDF...</p>
</div>
</div>
)}
{pdfData && !loading && (
<div className="flex justify-center">
<canvas
ref={canvasRef}
className="shadow-2xl rounded bg-white"
style={{ maxWidth: "100%", height: "auto" }}
/>
</div>
)}
</div>
</div>
);
}