From 957cc34f1292069afdf587ebae49135fe7f310ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Sz=C3=BCcs?= Date: Thu, 27 Aug 2026 22:57:04 +0200 Subject: [PATCH] feat(viewer): add font fallback, optimize lifecycle state and harden text selection --- .../public/locales/en-US/translation.toml | 5 + .../tools/redact/ManualRedactionControls.tsx | 55 +- .../viewer/ActiveDocumentContext.tsx | 79 --- .../AnnotationSelectionMenu.stories.tsx | 5 +- .../viewer/DocumentReadyWrapper.tsx | 50 +- .../core/components/viewer/EmbedPdfViewer.tsx | 8 +- .../core/components/viewer/LocalEmbedPDF.tsx | 632 ++++++++++-------- .../components/viewer/RedactionAPIBridge.tsx | 9 +- .../RedactionPendingTracker.stories.tsx | 5 +- .../viewer/RedactionSelectionMenu.stories.tsx | 5 +- .../components/viewer/SelectionAPIBridge.tsx | 48 +- .../core/components/viewer/ZoomAPIBridge.tsx | 74 +- .../viewer/hooks/useDocumentReady.ts | 71 +- .../components/viewer/useActiveDocumentId.ts | 17 +- .../src/core/contexts/RedactionContext.tsx | 8 +- .../core/services/pdfiumFontFallback.test.ts | 30 + .../src/core/services/pdfiumFontFallback.ts | 27 + .../src/core/services/wasmPrecompiler.ts | 48 +- .../stubbed/viewer-text-selection.spec.ts | 64 +- frontend/editor/src/core/utils/viewerZoom.ts | 8 +- frontend/editor/vite.config.ts | 6 + 21 files changed, 659 insertions(+), 595 deletions(-) delete mode 100644 frontend/editor/src/core/components/viewer/ActiveDocumentContext.tsx create mode 100644 frontend/editor/src/core/services/pdfiumFontFallback.test.ts create mode 100644 frontend/editor/src/core/services/pdfiumFontFallback.ts diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index 8271d66dfc..ff9efd6854 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -11716,8 +11716,12 @@ disableColorFilter = "Disable Color Filter" dualPageView = "Dual Page View" enableDarkFilter = "Enable Dark Filter" enableSepiaFilter = "Enable Sepia Filter" +engineLoadError = "Failed to initialize PDF viewer engine" +engineSlowWarning = "PDF engine initialization is taking longer than expected. Please check your browser WebAssembly and Worker settings, or reload the page." firstPage = "First Page" lastPage = "Last Page" +loadingDocument = "Loading document..." +loadingEngine = "Loading PDF Engine..." moreOptions = "More" nextPage = "Next Page" onlyPdfSupported = "This file format is not supported for preview." @@ -11828,6 +11832,7 @@ sortedBy = "Sorted by: {{column}}" textStats = "{{lines}} lines · {{size}}" [viewer.redaction] +applyAll = "Apply Redactions" removeMark = "Remove this mark" [viewer.search] diff --git a/frontend/editor/src/core/components/tools/redact/ManualRedactionControls.tsx b/frontend/editor/src/core/components/tools/redact/ManualRedactionControls.tsx index 49dc3b1da9..e3d3bd2f18 100644 --- a/frontend/editor/src/core/components/tools/redact/ManualRedactionControls.tsx +++ b/frontend/editor/src/core/components/tools/redact/ManualRedactionControls.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { useEffect, useRef, useCallback } from "react"; +import { useEffect, useRef, useCallback, useState } from "react"; import { Stack, Text, Divider, ColorInput } from "@mantine/core"; import { Button } from "@app/ui/Button"; import { useRedaction, useRedactionMode } from "@app/contexts/RedactionContext"; @@ -24,6 +24,7 @@ export default function ManualRedactionControls({ const { activateManualRedact, redactionsApplied, + commitAllPending, setActiveType, setManualRedactColor, } = useRedaction(); @@ -45,20 +46,22 @@ export default function ManualRedactionControls({ // Check if user is navigating away (modal shown) — don't fight the save/leave process const { showNavigationWarning } = useNavigationGuard(); - // Track the previous file index to detect file switches - const prevFileIndexRef = useRef(activeFileIndex); - - // Guard: pause auto-reactivation during save/export to avoid interfering with EmbedPDF - const isSavingRef = useRef(false); + const isLeavingRef = useRef(false); + const prevFileIndexRef = useRef(activeFileIndex); + const [isApplying, setIsApplying] = useState(false); + const [isSaving, setIsSaving] = useState(false); // Keep redaction tool active at all times while this component is mounted. // If anything deactivates it (annotation tools, text selection, file switch, etc.) // this re-enables it automatically — no manual "Activate" button needed. + // Activation is deferred so we never synchronously re-enter the effect in the + // same commit (which previously triggered React's "too many re-renders" error #185). useEffect(() => { if ( disabled || !isBridgeReady || - isSavingRef.current || + isLeavingRef.current || + isSaving || showNavigationWarning ) return; @@ -77,7 +80,7 @@ export default function ManualRedactionControls({ } // Small delay to avoid racing with EmbedPDF's own state updates const timer = setTimeout(() => { - if (!isSavingRef.current) { + if (!isLeavingRef.current && !isSaving && !showNavigationWarning) { activateManualRedact(); } }, 50); @@ -88,10 +91,11 @@ export default function ManualRedactionControls({ isAnnotationMode, disabled, isBridgeReady, + isSaving, showNavigationWarning, + activateManualRedact, setAnnotationMode, signatureApiRef, - activateManualRedact, ]); // Reset redaction tool when switching between files @@ -107,16 +111,23 @@ export default function ManualRedactionControls({ } }, [activeFileIndex, activeType, setActiveType]); + const handleApplyRedactions = useCallback(async () => { + setIsApplying(true); + try { + await commitAllPending(); + } finally { + setIsApplying(false); + } + }, [commitAllPending]); + // Handle saving changes - this will apply pending redactions and save to file const handleSaveChanges = useCallback(async () => { if (applyChanges) { - isSavingRef.current = true; + setIsSaving(true); try { await applyChanges(); - } catch { - // The viewer-level save handler reports the failure to the user. } finally { - isSavingRef.current = false; + setIsSaving(false); } } }, [applyChanges]); @@ -151,12 +162,26 @@ export default function ManualRedactionControls({ popoverProps={{ withinPortal: true }} /> + {pendingCount > 0 && ( + + )} + {/* Save Changes Button - applies pending redactions and saves to file */}