Skip to content

Commit 50bd85d

Browse files
author
Avenita
committed
Enhance file management and PDF handling in App component
- Implemented persistent file reordering and removal with updated state management. - Added error handling for saving file order to persistence. - Improved PDF page loading by utilizing pre-rendered images when available. - Refactored thumbnail loading logic to streamline PDF page rendering. - Updated ProjectorView to handle document types more effectively, ensuring proper image conversion.
1 parent 11b664e commit 50bd85d

File tree

4 files changed

+190
-71
lines changed

4 files changed

+190
-71
lines changed

src/components/App.tsx

Lines changed: 168 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,57 @@ export const App = () => {
319319
[handleSelectFiles]
320320
);
321321

322-
const handleReorder = (reorderedFiles: FileItem[]) => {
322+
const handleReorder = useCallback(async (reorderedFiles: FileItem[]) => {
323323
setFiles(reorderedFiles);
324-
};
324+
325+
// Save the new file order to persistence
326+
if (window.electronAPI) {
327+
try {
328+
// Load current files from persistence to get their data
329+
const result = await window.electronAPI.loadFiles();
330+
if (result.success && result.files) {
331+
// Build the new file order from reordered files
332+
const newFileOrder = reorderedFiles.flatMap((f) => {
333+
if (f.type === "document" && f.pages && f.pages.length > 0) {
334+
// For PDFs, include pages in order, then the main document
335+
return [...f.pages.map((p) => p.id), f.id];
336+
}
337+
return [f.id];
338+
});
339+
340+
// Create a map of file IDs to their data for quick lookup
341+
const filesDataMap = new Map(result.files.map((f) => [f.id, f]));
342+
343+
// Reorder files according to the new order
344+
const reorderedFilesData: Array<{
345+
id: string;
346+
name: string;
347+
type: string;
348+
data: string;
349+
pageNumber?: number;
350+
}> = [];
351+
352+
for (const fileId of newFileOrder) {
353+
const fileData = filesDataMap.get(fileId);
354+
if (fileData) {
355+
reorderedFilesData.push({
356+
id: fileData.id,
357+
name: fileData.name,
358+
type: fileData.type,
359+
data: fileData.data,
360+
pageNumber: fileData.pageNumber,
361+
});
362+
}
363+
}
364+
365+
// Save with the new order
366+
await window.electronAPI.saveFiles(reorderedFilesData, newFileOrder);
367+
}
368+
} catch (error) {
369+
console.error("Error saving file order to persistence:", error);
370+
}
371+
}
372+
}, []);
325373

326374
const handleRemove = async (id: string) => {
327375
// Find the file to get its type
@@ -337,7 +385,56 @@ export const App = () => {
337385
}
338386

339387
// Remove from state
340-
setFiles((prev) => prev.filter((file) => file.id !== id));
388+
const updatedFiles = files.filter((file) => file.id !== id);
389+
setFiles(updatedFiles);
390+
391+
// Save the updated file order to persistence
392+
if (window.electronAPI) {
393+
try {
394+
// Load current files from persistence to get their data
395+
const result = await window.electronAPI.loadFiles();
396+
if (result.success && result.files) {
397+
// Build the new file order from updated files (excluding the removed one)
398+
const newFileOrder = updatedFiles.flatMap((f) => {
399+
if (f.type === "document" && f.pages && f.pages.length > 0) {
400+
// For PDFs, include pages in order, then the main document
401+
return [...f.pages.map((p) => p.id), f.id];
402+
}
403+
return [f.id];
404+
});
405+
406+
// Create a map of file IDs to their data for quick lookup
407+
const filesDataMap = new Map(result.files.map((f) => [f.id, f]));
408+
409+
// Reorder files according to the new order, excluding the removed file
410+
const reorderedFilesData: Array<{
411+
id: string;
412+
name: string;
413+
type: string;
414+
data: string;
415+
pageNumber?: number;
416+
}> = [];
417+
418+
for (const fileId of newFileOrder) {
419+
const fileData = filesDataMap.get(fileId);
420+
if (fileData) {
421+
reorderedFilesData.push({
422+
id: fileData.id,
423+
name: fileData.name,
424+
type: fileData.type,
425+
data: fileData.data,
426+
pageNumber: fileData.pageNumber,
427+
});
428+
}
429+
}
430+
431+
// Save with the new order
432+
await window.electronAPI.saveFiles(reorderedFilesData, newFileOrder);
433+
}
434+
} catch (error) {
435+
console.error("Error saving file order after removal:", error);
436+
}
437+
}
341438
};
342439

343440
// Update projector when files change
@@ -794,7 +891,12 @@ export const App = () => {
794891
const loadedFiles: FileItem[] = [];
795892

796893
// Process each file (we're now saving full files, not page files)
797-
for (const savedFile of result.files) {
894+
// Filter out PDF page images - they're internal to PDF documents, not standalone files
895+
const mainFiles = result.files.filter(
896+
(f) => !f.id.includes("-page-") || f.type === "document"
897+
);
898+
899+
for (const savedFile of mainFiles) {
798900
try {
799901
const fileType: "image" | "video" | "document" =
800902
savedFile.type === "video"
@@ -825,9 +927,33 @@ export const App = () => {
825927
.promise;
826928
const numPages = pdf.numPages;
827929

930+
// Get the saved file order to determine page order
931+
const savedFileOrder = result.fileOrder || [];
932+
933+
// Find all page IDs for this PDF in the saved order
934+
const pageIdsInOrder = savedFileOrder.filter((id) =>
935+
id.startsWith(`${savedFile.id}-page-`)
936+
);
937+
828938
const pages: PdfPage[] = [];
829-
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
830-
const pageId = `${savedFile.id}-page-${pageNum}`;
939+
940+
// If we have a saved order, use it; otherwise use default order (1 to numPages)
941+
const pageIdsToLoad =
942+
pageIdsInOrder.length > 0
943+
? pageIdsInOrder
944+
: Array.from(
945+
{ length: numPages },
946+
(_, i) => `${savedFile.id}-page-${i + 1}`
947+
);
948+
949+
for (const pageId of pageIdsToLoad) {
950+
// Extract page number from pageId (format: fileId-page-N)
951+
const pageNumMatch = pageId.match(/-page-(\d+)$/);
952+
if (!pageNumMatch) continue;
953+
954+
const pageNum = parseInt(pageNumMatch[1], 10);
955+
if (pageNum < 1 || pageNum > numPages) continue;
956+
831957
// Try to find pre-rendered image in the loaded files
832958
let imageData: string | undefined;
833959
const pageFile = result.files?.find((f) => f.id === pageId);
@@ -936,7 +1062,12 @@ export const App = () => {
9361062
if (data && data.files && data.files.length > 0) {
9371063
const loadedFiles: FileItem[] = [];
9381064

939-
for (const savedFile of data.files) {
1065+
// Filter out PDF page images - they're internal to PDF documents, not standalone files
1066+
const mainFiles = data.files.filter(
1067+
(f) => !f.id.includes("-page-") || f.type === "document"
1068+
);
1069+
1070+
for (const savedFile of mainFiles) {
9401071
try {
9411072
const fileType: "image" | "video" | "document" =
9421073
savedFile.type === "video"
@@ -945,7 +1076,6 @@ export const App = () => {
9451076
? "document"
9461077
: "image";
9471078

948-
// Convert base64 to File object
9491079
const base64Data = savedFile.data.split(",")[1] || savedFile.data;
9501080
const mimeType = savedFile.data
9511081
.split(",")[0]
@@ -967,21 +1097,38 @@ export const App = () => {
9671097
.promise;
9681098
const numPages = pdf.numPages;
9691099

1100+
// Get the saved file order to determine page order
1101+
const savedFileOrder = data.fileOrder || [];
1102+
1103+
// Find all page IDs for this PDF in the saved order
1104+
const pageIdsInOrder = savedFileOrder.filter((id) =>
1105+
id.startsWith(`${savedFile.id}-page-`)
1106+
);
1107+
9701108
const pages: PdfPage[] = [];
971-
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
972-
const pageId = `${savedFile.id}-page-${pageNum}`;
973-
// Try to load pre-rendered image from persistence
1109+
1110+
// If we have a saved order, use it; otherwise use default order (1 to numPages)
1111+
const pageIdsToLoad =
1112+
pageIdsInOrder.length > 0
1113+
? pageIdsInOrder
1114+
: Array.from(
1115+
{ length: numPages },
1116+
(_, i) => `${savedFile.id}-page-${i + 1}`
1117+
);
1118+
1119+
for (const pageId of pageIdsToLoad) {
1120+
// Extract page number from pageId (format: fileId-page-N)
1121+
const pageNumMatch = pageId.match(/-page-(\d+)$/);
1122+
if (!pageNumMatch) continue;
1123+
1124+
const pageNum = parseInt(pageNumMatch[1], 10);
1125+
if (pageNum < 1 || pageNum > numPages) continue;
1126+
1127+
// Try to find pre-rendered image in the loaded files
9741128
let imageData: string | undefined;
975-
try {
976-
const result = await window.electronAPI!.loadFiles();
977-
if (result.success && result.files) {
978-
const pageFile = result.files.find((f) => f.id === pageId);
979-
if (pageFile) {
980-
imageData = pageFile.data;
981-
}
982-
}
983-
} catch (e) {
984-
// Image not found, will be re-rendered if needed
1129+
const pageFile = data.files.find((f) => f.id === pageId);
1130+
if (pageFile) {
1131+
imageData = pageFile.data;
9851132
}
9861133

9871134
// If no pre-rendered image, render it now

src/components/FileThumbnail.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface FileThumbnailProps {
1717
className?: string;
1818
pageNumber?: number; // For PDF pages
1919
fileId?: string; // File ID for loading saved thumbnails
20+
preRenderedImage?: string; // Pre-rendered image data (base64) for PDF pages
2021
}
2122

2223
export const FileThumbnail = ({
@@ -25,6 +26,7 @@ export const FileThumbnail = ({
2526
className,
2627
pageNumber,
2728
fileId,
29+
preRenderedImage,
2830
}: FileThumbnailProps) => {
2931
const [thumbnailUrl, setThumbnailUrl] = useState<string | null>(null);
3032
const [error, setError] = useState(false);
@@ -90,6 +92,13 @@ export const FileThumbnail = ({
9092

9193
const loadPdfThumbnail = async () => {
9294
try {
95+
// If we have a pre-rendered image, use it directly
96+
if (preRenderedImage) {
97+
setThumbnailUrl(preRenderedImage);
98+
setIsLoading(false);
99+
return;
100+
}
101+
93102
// Wait a bit to ensure canvas is mounted
94103
await new Promise((resolve) => setTimeout(resolve, 0));
95104

@@ -260,7 +269,7 @@ export const FileThumbnail = ({
260269
loadThumbnail();
261270

262271
return cleanup;
263-
}, [file, type, fileId, pageNumber]);
272+
}, [file, type, fileId, pageNumber, preRenderedImage]);
264273

265274
const getIcon = () => {
266275
switch (type) {

src/components/PdfFileItem.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const SortablePageItem = ({
9999
className="w-full h-full"
100100
pageNumber={page.pageNumber}
101101
fileId={`${fileId}-page-${page.pageNumber}`}
102+
preRenderedImage={page.imageData}
102103
/>
103104
</div>
104105
<div className="flex-1 min-w-0">
@@ -243,6 +244,7 @@ export const PdfFileItem = ({
243244
className="w-full h-full"
244245
pageNumber={pages[0]?.pageNumber || 1}
245246
fileId={file.id}
247+
preRenderedImage={pages[0]?.imageData}
246248
/>
247249
</div>
248250
<div className="flex-1 min-w-0">

src/components/ProjectorView.tsx

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -617,56 +617,17 @@ export const ProjectorView = () => {
617617
}
618618

619619
const currentFile = files[currentIndex];
620+
// PDF pages are converted to images before being sent to projector
621+
// So we should never need to render from PDF here
620622
if (currentFile.type === "document") {
621-
const generatePdfThumbnail = async () => {
622-
try {
623-
const canvas = canvasRef.current;
624-
if (!canvas) return;
625-
626-
// If data is already a rendered page image, use it directly
627-
if (currentFile.data.startsWith("data:image")) {
628-
setPdfThumbnail(currentFile.data);
629-
return;
630-
}
631-
632-
// Otherwise, render the PDF page
633-
const response = await fetch(currentFile.data);
634-
const arrayBuffer = await response.arrayBuffer();
635-
636-
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
637-
const pageToRender = currentFile.pageNumber || 1;
638-
const page = await pdf.getPage(pageToRender);
639-
640-
const viewport = page.getViewport({ scale: 1.0 });
641-
const maxDimension = Math.max(window.innerWidth, window.innerHeight);
642-
const scale = Math.min(
643-
maxDimension / viewport.width,
644-
maxDimension / viewport.height,
645-
2.0
646-
);
647-
const scaledViewport = page.getViewport({ scale });
648-
649-
canvas.width = scaledViewport.width;
650-
canvas.height = scaledViewport.height;
651-
652-
const context = canvas.getContext("2d");
653-
if (!context) return;
654-
655-
await page.render({
656-
canvasContext: context,
657-
viewport: scaledViewport,
658-
canvas: canvas,
659-
}).promise;
660-
661-
const dataUrl = canvas.toDataURL("image/jpeg", 0.9);
662-
setPdfThumbnail(dataUrl);
663-
} catch (error) {
664-
console.error("Error generating PDF thumbnail:", error);
665-
setPdfThumbnail(null);
666-
}
667-
};
668-
669-
generatePdfThumbnail();
623+
// If we receive a document type, it should have been converted to an image
624+
// Log a warning and try to use the data as-is if it's already an image
625+
if (currentFile.data.startsWith("data:image")) {
626+
setPdfThumbnail(currentFile.data);
627+
} else {
628+
console.warn("Document type received in projector without image data. PDFs should be converted to images before projection.");
629+
setPdfThumbnail(null);
630+
}
670631
} else {
671632
setPdfThumbnail(null);
672633
}

0 commit comments

Comments
 (0)