diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index 70695091f..10e923c90 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -2256,34 +2256,7 @@ }, "clear": "Clear", "add": "Add", - "saved": { - "heading": "Saved signatures", - "description": "Reuse saved signatures at any time.", - "emptyTitle": "No saved signatures yet", - "emptyDescription": "Draw, upload, or type a signature above, then use \"Save to library\" to keep up to {{max}} favourites ready to use.", - "type": { - "canvas": "Drawing", - "image": "Upload", - "text": "Text" - }, - "limitTitle": "Limit reached", - "limitDescription": "Remove a saved signature before adding new ones (max {{max}}).", - "carouselPosition": "{{current}} of {{total}}", - "prev": "Previous", - "next": "Next", - "delete": "Remove", - "label": "Label", - "defaultLabel": "Signature", - "defaultCanvasLabel": "Drawing signature", - "defaultImageLabel": "Uploaded signature", - "defaultTextLabel": "Typed signature", - "saveButton": "Save signature", - "saveUnavailable": "Create a signature first to save it.", - "noChanges": "Current signature is already saved.", - "status": { - "saved": "Saved" - } - }, + "saved": "Saved Signatures", "save": "Save Signature", "applySignatures": "Apply Signatures", "personalSigs": "Personal Signatures", @@ -2318,7 +2291,6 @@ "title": "How to add signature", "canvas": "After drawing your signature in the canvas, close the modal then click anywhere on the PDF to place it.", "image": "After uploading your signature image above, click anywhere on the PDF to place it.", - "saved": "Select a saved signature above, then click anywhere on the PDF to place it.", "text": "After entering your name above, click anywhere on the PDF to place your signature." }, "mode": { diff --git a/frontend/src/core/components/annotation/shared/DrawingCanvas.tsx b/frontend/src/core/components/annotation/shared/DrawingCanvas.tsx index 67476db68..7e3324a97 100644 --- a/frontend/src/core/components/annotation/shared/DrawingCanvas.tsx +++ b/frontend/src/core/components/annotation/shared/DrawingCanvas.tsx @@ -283,7 +283,7 @@ export const DrawingCanvas: React.FC = ({ touchAction: 'none', backgroundColor: 'white', width: '100%', - maxWidth: '800px', + maxWidth: '50rem', height: '25rem', cursor: 'crosshair', }} diff --git a/frontend/src/core/components/tools/sign/SavedSignaturesSection.tsx b/frontend/src/core/components/tools/sign/SavedSignaturesSection.tsx index dbdab551b..fa665c7f3 100644 --- a/frontend/src/core/components/tools/sign/SavedSignaturesSection.tsx +++ b/frontend/src/core/components/tools/sign/SavedSignaturesSection.tsx @@ -31,7 +31,6 @@ export const SavedSignaturesSection = ({ const [labelDrafts, setLabelDrafts] = useState>({}); const [activeIndex, setActiveIndex] = useState(0); const activeSignature = signatures[activeIndex]; - const activeSignatureRef = useRef(activeSignature ?? null); const appliedSignatureIdRef = useRef(null); const onUseSignatureRef = useRef(onUseSignature); @@ -39,10 +38,6 @@ export const SavedSignaturesSection = ({ onUseSignatureRef.current = onUseSignature; }, [onUseSignature]); - useEffect(() => { - activeSignatureRef.current = activeSignature ?? null; - }, [activeSignature]); - useEffect(() => { setLabelDrafts(prev => { const nextDrafts: Record = {}; @@ -187,19 +182,18 @@ export const SavedSignaturesSection = ({ }; useEffect(() => { - const signature = activeSignatureRef.current; - if (!signature || disabled) { + if (!activeSignature || disabled) { appliedSignatureIdRef.current = null; return; } - if (appliedSignatureIdRef.current === signature.id) { + if (appliedSignatureIdRef.current === activeSignature.id) { return; } - appliedSignatureIdRef.current = signature.id; - onUseSignatureRef.current(signature); - }, [activeSignature?.id, disabled]); + appliedSignatureIdRef.current = activeSignature.id; + onUseSignatureRef.current(activeSignature); + }, [activeSignature, disabled]); return ( diff --git a/frontend/src/core/components/tools/sign/SignSettings.tsx b/frontend/src/core/components/tools/sign/SignSettings.tsx index a0a1bd8db..ea5d237c7 100644 --- a/frontend/src/core/components/tools/sign/SignSettings.tsx +++ b/frontend/src/core/components/tools/sign/SignSettings.tsx @@ -588,7 +588,9 @@ const SignSettings = ({ const timer = window.setTimeout(() => { onActivateSignaturePlacement?.(); }, PLACEMENT_ACTIVATION_DELAY); - return () => window.clearTimeout(timer); + return () => { + window.clearTimeout(timer); + }; } onActivateSignaturePlacement?.(); @@ -625,7 +627,9 @@ const SignSettings = ({ if (typeof window !== 'undefined') { const timer = window.setTimeout(trigger, PLACEMENT_ACTIVATION_DELAY); - return () => window.clearTimeout(timer); + return () => { + window.clearTimeout(timer); + }; } trigger(); @@ -648,7 +652,9 @@ const SignSettings = ({ const timer = window.setTimeout(() => { onActivateSignaturePlacement?.(); }, FILE_SWITCH_ACTIVATION_DELAY); - return () => window.clearTimeout(timer); + return () => { + window.clearTimeout(timer); + }; } onActivateSignaturePlacement?.(); diff --git a/frontend/src/core/components/viewer/HistoryAPIBridge.tsx b/frontend/src/core/components/viewer/HistoryAPIBridge.tsx index ff03d687f..8c463d8e2 100644 --- a/frontend/src/core/components/viewer/HistoryAPIBridge.tsx +++ b/frontend/src/core/components/viewer/HistoryAPIBridge.tsx @@ -4,6 +4,7 @@ import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react'; import { useSignature } from '@app/contexts/SignatureContext'; import { uuidV4 } from '@embedpdf/models'; import type { HistoryAPI } from '@app/components/viewer/viewerTypes'; +import { ANNOTATION_RECREATION_DELAY_MS, ANNOTATION_VERIFICATION_DELAY_MS } from '@app/core/constants/app'; export const HistoryAPIBridge = forwardRef(function HistoryAPIBridge(_, ref) { const { provides: historyApi } = useHistoryCapability(); @@ -59,7 +60,7 @@ export const HistoryAPIBridge = forwardRef(function HistoryAPIBridge data: storedImageData, appearance: storedImageData, }); - }, 50); + }, ANNOTATION_RECREATION_DELAY_MS); } catch (restoreError) { console.error('HistoryAPI: Failed to restore cropped signature:', restoreError); } @@ -103,12 +104,12 @@ export const HistoryAPIBridge = forwardRef(function HistoryAPIBridge // Small delay to ensure deletion completes setTimeout(() => { annotationApi.createAnnotation(event.pageIndex, restoredData); - }, 50); + }, ANNOTATION_RECREATION_DELAY_MS); } catch (error) { console.error('HistoryAPI: Failed to restore annotation:', error); } } - }, 100); + }, ANNOTATION_VERIFICATION_DELAY_MS); } } }; diff --git a/frontend/src/core/components/viewer/SignatureAPIBridge.tsx b/frontend/src/core/components/viewer/SignatureAPIBridge.tsx index 200d5e43f..c222972eb 100644 --- a/frontend/src/core/components/viewer/SignatureAPIBridge.tsx +++ b/frontend/src/core/components/viewer/SignatureAPIBridge.tsx @@ -14,6 +14,14 @@ const MIN_SIGNATURE_DIMENSION = 12; // This provides a good balance between visual fidelity and performance/memory usage. const TEXT_OVERSAMPLE_FACTOR = 2; +type TextStampImageResult = { + dataUrl: string; + pixelWidth: number; + pixelHeight: number; + displayWidth: number; + displayHeight: number; +}; + const extractDataUrl = (value: unknown, depth = 0, visited: Set = new Set()): string | undefined => { if (!value || depth > 6) return undefined; @@ -48,7 +56,7 @@ const extractDataUrl = (value: unknown, depth = 0, visited: Set = new S const createTextStampImage = ( config: SignParameters, displaySize?: { width: number; height: number } | null -): { dataUrl: string; pixelWidth: number; pixelHeight: number; displayWidth: number; displayHeight: number } | null => { +): TextStampImageResult | null => { const text = (config.signerName ?? '').trim(); if (!text) { return null; diff --git a/frontend/src/core/constants/app.ts b/frontend/src/core/constants/app.ts index c61302c84..04057fa37 100644 --- a/frontend/src/core/constants/app.ts +++ b/frontend/src/core/constants/app.ts @@ -3,6 +3,11 @@ // When no subpath, use empty string instead of '.' to avoid relative path issues export const BASE_PATH = (import.meta.env.BASE_URL || '/').replace(/\/$/, '').replace(/^\.$/, ''); +// EmbedPDF needs time to remove annotations internally before a recreation runs. +// Without the buffer we occasionally end up with duplicate annotations or stale image data. +export const ANNOTATION_RECREATION_DELAY_MS = 50; +export const ANNOTATION_VERIFICATION_DELAY_MS = 100; + /** For in-app navigations when you must touch window.location (rare). */ export const withBasePath = (path: string): string => { const clean = path.startsWith('/') ? path : `/${path}`; diff --git a/frontend/src/core/hooks/tools/sign/useSavedSignatures.ts b/frontend/src/core/hooks/tools/sign/useSavedSignatures.ts index 665f40814..85ed04ad6 100644 --- a/frontend/src/core/hooks/tools/sign/useSavedSignatures.ts +++ b/frontend/src/core/hooks/tools/sign/useSavedSignatures.ts @@ -97,12 +97,7 @@ const writeToStorage = (entries: SavedSignature[]) => { } }; -const generateId = () => { - if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { - return crypto.randomUUID(); - } - return `sig_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`; -}; +const generateId = () => crypto.randomUUID(); export const useSavedSignatures = () => { const [savedSignatures, setSavedSignatures] = useState(() => readFromStorage());