File size: 5,114 Bytes
dd30f85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d87e2c
dd30f85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import React, { useEffect, useState, useRef } from "react";
import * as pdfjsLib from "pdfjs-dist";
import { fetchNoteBlob } from "../api/notesService";
import {
  ChevronLeft,
  ChevronRight,
  ZoomIn,
  ZoomOut,
  Loader2,
} from "lucide-react";

// Configure PDF Worker (Required for pdfjs-dist)
pdfjsLib.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;

interface Props {
  noteId: number;
}

const SecurePdfViewer: React.FC<Props> = ({ noteId }) => {
  const [pdfDoc, setPdfDoc] = useState<pdfjsLib.PDFDocumentProxy | null>(null);
  const [pageNum, setPageNum] = useState(1);
  const [scale, setScale] = useState(1.2);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);

  // 1. Fetch and Load PDF Data
  useEffect(() => {
    let isMounted = true;
    const loadPdf = async () => {
      try {
        setLoading(true);
        setError(null);

        // Fetch securely using existing Axios setup (sends Auth header)
        const blob = await fetchNoteBlob(noteId);
        const arrayBuffer = await blob.arrayBuffer();

        // Load into PDF.js
        const loadedPdf = await pdfjsLib.getDocument({ data: arrayBuffer })
          .promise;

        if (isMounted) {
          setPdfDoc(loadedPdf);
          setPageNum(1);
          setLoading(false);
        }
      } catch (err) {
        console.error("PDF Load Error:", err);
        if (isMounted) setError("Failed to load PDF. Please try again.");
        setLoading(false);
      }
    };

    if (noteId) loadPdf();

    return () => {
      isMounted = false;
    };
  }, [noteId]);

  // 2. Render Page on Canvas
  useEffect(() => {
    if (!pdfDoc || !canvasRef.current) return;

    const renderPage = async () => {
      try {
        const page = await pdfDoc.getPage(pageNum);
        const viewport = page.getViewport({ scale });
        const canvas = canvasRef.current!;
        const context = canvas.getContext("2d")!;

        // Handle High DPI screens
        const outputScale = window.devicePixelRatio || 1;
        canvas.width = Math.floor(viewport.width * outputScale);
        canvas.height = Math.floor(viewport.height * outputScale);
        canvas.style.width = Math.floor(viewport.width) + "px";
        canvas.style.height = Math.floor(viewport.height) + "px";

        const transform =
          outputScale !== 1
            ? [outputScale, 0, 0, outputScale, 0, 0]
            : undefined;

        await page.render({
          canvasContext: context,
          canvas: canvas, // <--- FIX APPLIED HERE
          viewport: viewport,
          transform: transform,
        }).promise;
      } catch (err) {
        console.error("Page Render Error:", err);
      }
    };

    renderPage();
  }, [pdfDoc, pageNum, scale]);

  if (loading) {
    return (
      <div className="flex flex-col items-center justify-center h-full text-white">
        <Loader2 className="w-8 h-8 animate-spin text-[#F7E396] mb-2" />
        <p>Securely loading document...</p>
      </div>
    );
  }

  if (error) {
    return <div className="text-red-400 p-4 text-center">{error}</div>;
  }

  return (
    <div className="flex flex-col h-full bg-[#525f88] rounded-xl overflow-hidden relative">
      {/* Toolbar */}
      <div className="flex items-center justify-between p-2 bg-[#434E78] border-b border-white/10 text-white z-10 shadow-md">
        <div className="flex items-center gap-2">
          <button
            disabled={pageNum <= 1}
            onClick={() => setPageNum((p) => p - 1)}
            className="p-1 hover:bg-white/10 rounded disabled:opacity-30 transition"
          >
            <ChevronLeft size={20} />
          </button>
          <span className="text-sm font-medium w-16 text-center">
            {pageNum} / {pdfDoc?.numPages}
          </span>
          <button
            disabled={!pdfDoc || pageNum >= pdfDoc.numPages}
            onClick={() => setPageNum((p) => p + 1)}
            className="p-1 hover:bg-white/10 rounded disabled:opacity-30 transition"
          >
            <ChevronRight size={20} />
          </button>
        </div>

        <div className="flex items-center gap-2">
          <button
            onClick={() => setScale((s) => Math.max(0.5, s - 0.2))}
            className="p-1 hover:bg-white/10 rounded transition"
          >
            <ZoomOut size={18} />
          </button>
          <span className="text-xs w-12 text-center">
            {Math.round(scale * 100)}%
          </span>
          <button
            onClick={() => setScale((s) => Math.min(3.0, s + 0.2))}
            className="p-1 hover:bg-white/10 rounded transition"
          >
            <ZoomIn size={18} />
          </button>
        </div>
      </div>

      {/* Scrollable Canvas Area */}
      <div className="flex-1 overflow-auto flex justify-center p-4 bg-[#525f88] custom-scrollbar">
        <canvas ref={canvasRef} className="shadow-2xl" />
      </div>
    </div>
  );
};

export default SecurePdfViewer;