Seth0330 commited on
Commit
498b975
·
verified ·
1 Parent(s): a360593

Update frontend/src/components/ocr/DocumentPreview.jsx

Browse files
frontend/src/components/ocr/DocumentPreview.jsx CHANGED
@@ -1,11 +1,92 @@
1
- import React from "react";
2
  import { motion } from "framer-motion";
3
  import { FileText, ZoomIn, ZoomOut, RotateCw, Maximize2 } from "lucide-react";
4
  import { Button } from "@/components/ui/button";
5
 
6
  export default function DocumentPreview({ file, isProcessing }) {
7
- // Mock preview - in real app would show actual document
8
- const mockPages = [1, 2, 3];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  return (
11
  <div className="h-full flex flex-col bg-white rounded-2xl border border-slate-200 overflow-hidden">
@@ -27,14 +108,16 @@ export default function DocumentPreview({ file, isProcessing }) {
27
  variant="ghost"
28
  size="icon"
29
  className="h-8 w-8 text-slate-400 hover:text-slate-600"
 
30
  >
31
  <ZoomOut className="h-4 w-4" />
32
  </Button>
33
- <span className="text-xs text-slate-500 w-12 text-center">100%</span>
34
  <Button
35
  variant="ghost"
36
  size="icon"
37
  className="h-8 w-8 text-slate-400 hover:text-slate-600"
 
38
  >
39
  <ZoomIn className="h-4 w-4" />
40
  </Button>
@@ -43,6 +126,7 @@ export default function DocumentPreview({ file, isProcessing }) {
43
  variant="ghost"
44
  size="icon"
45
  className="h-8 w-8 text-slate-400 hover:text-slate-600"
 
46
  >
47
  <RotateCw className="h-4 w-4" />
48
  </Button>
@@ -50,6 +134,10 @@ export default function DocumentPreview({ file, isProcessing }) {
50
  variant="ghost"
51
  size="icon"
52
  className="h-8 w-8 text-slate-400 hover:text-slate-600"
 
 
 
 
53
  >
54
  <Maximize2 className="h-4 w-4" />
55
  </Button>
@@ -68,61 +156,52 @@ export default function DocumentPreview({ file, isProcessing }) {
68
  <p className="text-slate-400 text-sm">Upload a document to preview</p>
69
  </div>
70
  </div>
 
 
 
 
 
 
 
 
 
71
  ) : (
72
  <div className="space-y-4">
73
- {mockPages.map((page, index) => (
74
  <motion.div
75
- key={page}
76
  initial={{ opacity: 0, y: 20 }}
77
  animate={{ opacity: 1, y: 0 }}
78
  transition={{ delay: index * 0.1 }}
79
- className="relative bg-white rounded-xl shadow-sm border border-slate-200 aspect-[8.5/11] overflow-hidden"
 
 
 
80
  >
81
- {/* Mock document content */}
82
- <div className="absolute inset-0 p-8">
83
- {/* Header simulation */}
84
- <div className="flex items-center gap-4 mb-8">
85
- <div className="h-12 w-12 rounded-lg bg-slate-100" />
86
- <div className="space-y-2">
87
- <div className="h-3 w-32 bg-slate-100 rounded" />
88
- <div className="h-2 w-24 bg-slate-50 rounded" />
89
- </div>
90
- </div>
91
-
92
- {/* Text lines simulation */}
93
- <div className="space-y-3">
94
- {[...Array(12)].map((_, i) => (
95
- <div
96
- key={i}
97
- className="h-2 bg-slate-100 rounded"
98
- style={{ width: `${Math.random() * 40 + 60}%` }}
99
- />
100
- ))}
101
- </div>
102
-
103
- {/* Table simulation */}
104
- <div className="mt-6 border border-slate-100 rounded-lg overflow-hidden">
105
- {[...Array(4)].map((_, i) => (
106
- <div key={i} className="flex border-b border-slate-50 last:border-0">
107
- {[...Array(3)].map((_, j) => (
108
- <div
109
- key={j}
110
- className="flex-1 p-3 border-r border-slate-50 last:border-0"
111
- >
112
- <div className="h-2 bg-slate-100 rounded w-3/4" />
113
- </div>
114
- ))}
115
- </div>
116
- ))}
117
  </div>
118
- </div>
119
 
120
  {/* Processing overlay */}
121
  {isProcessing && (
122
  <motion.div
123
  initial={{ opacity: 0 }}
124
  animate={{ opacity: 1 }}
125
- className="absolute inset-0 bg-indigo-600/5 backdrop-blur-[1px]"
126
  >
127
  <motion.div
128
  initial={{ top: 0 }}
@@ -138,9 +217,11 @@ export default function DocumentPreview({ file, isProcessing }) {
138
  )}
139
 
140
  {/* Page number */}
141
- <div className="absolute bottom-3 right-3 text-xs text-slate-400 bg-white/90 px-2 py-1 rounded">
142
- Page {page}
143
- </div>
 
 
144
  </motion.div>
145
  ))}
146
  </div>
 
1
+ import React, { useState, useEffect, useRef } from "react";
2
  import { motion } from "framer-motion";
3
  import { FileText, ZoomIn, ZoomOut, RotateCw, Maximize2 } from "lucide-react";
4
  import { Button } from "@/components/ui/button";
5
 
6
  export default function DocumentPreview({ file, isProcessing }) {
7
+ const [previewUrls, setPreviewUrls] = useState([]);
8
+ const [zoom, setZoom] = useState(100);
9
+ const [rotation, setRotation] = useState(0);
10
+ const objectUrlsRef = useRef([]);
11
+
12
+ useEffect(() => {
13
+ if (!file) {
14
+ // Cleanup previous URLs
15
+ objectUrlsRef.current.forEach((url) => {
16
+ if (url && url.startsWith("blob:")) {
17
+ URL.revokeObjectURL(url);
18
+ }
19
+ });
20
+ objectUrlsRef.current = [];
21
+ setPreviewUrls([]);
22
+ return;
23
+ }
24
+
25
+ const loadPreview = async () => {
26
+ const urls = [];
27
+ const newObjectUrls = [];
28
+
29
+ // Check if it's a PDF
30
+ if (file.type === "application/pdf" || file.name?.toLowerCase().endsWith(".pdf")) {
31
+ try {
32
+ // Use pdf.js to render PDF pages
33
+ const pdfjsLib = await import("pdfjs-dist");
34
+ pdfjsLib.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.js`;
35
+
36
+ const arrayBuffer = await file.arrayBuffer();
37
+ const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
38
+ const numPages = pdf.numPages;
39
+
40
+ for (let pageNum = 1; pageNum <= numPages; pageNum++) {
41
+ const page = await pdf.getPage(pageNum);
42
+ const viewport = page.getViewport({ scale: 2.0 });
43
+
44
+ const canvas = document.createElement("canvas");
45
+ const context = canvas.getContext("2d");
46
+ canvas.height = viewport.height;
47
+ canvas.width = viewport.width;
48
+
49
+ await page.render({
50
+ canvasContext: context,
51
+ viewport: viewport,
52
+ }).promise;
53
+
54
+ urls.push(canvas.toDataURL("image/jpeg", 0.95));
55
+ }
56
+ } catch (error) {
57
+ console.error("Error loading PDF:", error);
58
+ // Fallback: show error message
59
+ urls.push(null);
60
+ }
61
+ } else {
62
+ // For images, create object URL
63
+ const url = URL.createObjectURL(file);
64
+ urls.push(url);
65
+ newObjectUrls.push(url);
66
+ }
67
+
68
+ // Cleanup old object URLs
69
+ objectUrlsRef.current.forEach((url) => {
70
+ if (url && url.startsWith("blob:")) {
71
+ URL.revokeObjectURL(url);
72
+ }
73
+ });
74
+ objectUrlsRef.current = newObjectUrls;
75
+ setPreviewUrls(urls);
76
+ };
77
+
78
+ loadPreview();
79
+
80
+ // Cleanup function - revoke object URLs when component unmounts or file changes
81
+ return () => {
82
+ objectUrlsRef.current.forEach((url) => {
83
+ if (url && url.startsWith("blob:")) {
84
+ URL.revokeObjectURL(url);
85
+ }
86
+ });
87
+ objectUrlsRef.current = [];
88
+ };
89
+ }, [file]);
90
 
91
  return (
92
  <div className="h-full flex flex-col bg-white rounded-2xl border border-slate-200 overflow-hidden">
 
108
  variant="ghost"
109
  size="icon"
110
  className="h-8 w-8 text-slate-400 hover:text-slate-600"
111
+ onClick={() => setZoom(Math.max(50, zoom - 25))}
112
  >
113
  <ZoomOut className="h-4 w-4" />
114
  </Button>
115
+ <span className="text-xs text-slate-500 w-12 text-center">{zoom}%</span>
116
  <Button
117
  variant="ghost"
118
  size="icon"
119
  className="h-8 w-8 text-slate-400 hover:text-slate-600"
120
+ onClick={() => setZoom(Math.min(200, zoom + 25))}
121
  >
122
  <ZoomIn className="h-4 w-4" />
123
  </Button>
 
126
  variant="ghost"
127
  size="icon"
128
  className="h-8 w-8 text-slate-400 hover:text-slate-600"
129
+ onClick={() => setRotation((rotation + 90) % 360)}
130
  >
131
  <RotateCw className="h-4 w-4" />
132
  </Button>
 
134
  variant="ghost"
135
  size="icon"
136
  className="h-8 w-8 text-slate-400 hover:text-slate-600"
137
+ onClick={() => {
138
+ setZoom(100);
139
+ setRotation(0);
140
+ }}
141
  >
142
  <Maximize2 className="h-4 w-4" />
143
  </Button>
 
156
  <p className="text-slate-400 text-sm">Upload a document to preview</p>
157
  </div>
158
  </div>
159
+ ) : previewUrls.length === 0 ? (
160
+ <div className="h-full flex items-center justify-center">
161
+ <div className="text-center">
162
+ <div className="h-20 w-20 mx-auto rounded-2xl bg-slate-100 flex items-center justify-center mb-4">
163
+ <FileText className="h-10 w-10 text-slate-300" />
164
+ </div>
165
+ <p className="text-slate-400 text-sm">Loading preview...</p>
166
+ </div>
167
+ </div>
168
  ) : (
169
  <div className="space-y-4">
170
+ {previewUrls.map((url, index) => (
171
  <motion.div
172
+ key={index}
173
  initial={{ opacity: 0, y: 20 }}
174
  animate={{ opacity: 1, y: 0 }}
175
  transition={{ delay: index * 0.1 }}
176
+ className="relative bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden flex items-center justify-center"
177
+ style={{
178
+ minHeight: "400px",
179
+ }}
180
  >
181
+ {url ? (
182
+ <img
183
+ src={url}
184
+ alt={`Page ${index + 1}`}
185
+ className="w-full h-auto"
186
+ style={{
187
+ transform: `scale(${zoom / 100}) rotate(${rotation}deg)`,
188
+ maxWidth: "100%",
189
+ objectFit: "contain",
190
+ transition: "transform 0.2s ease",
191
+ }}
192
+ />
193
+ ) : (
194
+ <div className="p-8 text-center">
195
+ <p className="text-slate-400 text-sm">Unable to load preview</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  </div>
197
+ )}
198
 
199
  {/* Processing overlay */}
200
  {isProcessing && (
201
  <motion.div
202
  initial={{ opacity: 0 }}
203
  animate={{ opacity: 1 }}
204
+ className="absolute inset-0 bg-indigo-600/5 backdrop-blur-[1px] pointer-events-none"
205
  >
206
  <motion.div
207
  initial={{ top: 0 }}
 
217
  )}
218
 
219
  {/* Page number */}
220
+ {previewUrls.length > 1 && (
221
+ <div className="absolute bottom-3 right-3 text-xs text-slate-400 bg-white/90 px-2 py-1 rounded">
222
+ Page {index + 1}
223
+ </div>
224
+ )}
225
  </motion.div>
226
  ))}
227
  </div>