This commit is contained in:
Reece 2025-11-14 13:57:37 +00:00
parent c8bf43ea6b
commit c4f8c42a5a
8 changed files with 35 additions and 54 deletions

View File

@ -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": {

View File

@ -283,7 +283,7 @@ export const DrawingCanvas: React.FC<DrawingCanvasProps> = ({
touchAction: 'none',
backgroundColor: 'white',
width: '100%',
maxWidth: '800px',
maxWidth: '50rem',
height: '25rem',
cursor: 'crosshair',
}}

View File

@ -31,7 +31,6 @@ export const SavedSignaturesSection = ({
const [labelDrafts, setLabelDrafts] = useState<Record<string, string>>({});
const [activeIndex, setActiveIndex] = useState(0);
const activeSignature = signatures[activeIndex];
const activeSignatureRef = useRef<SavedSignature | null>(activeSignature ?? null);
const appliedSignatureIdRef = useRef<string | null>(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<string, string> = {};
@ -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 (
<Stack gap="sm">

View File

@ -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?.();

View File

@ -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<HistoryAPI>(function HistoryAPIBridge(_, ref) {
const { provides: historyApi } = useHistoryCapability();
@ -59,7 +60,7 @@ export const HistoryAPIBridge = forwardRef<HistoryAPI>(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<HistoryAPI>(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);
}
}
};

View File

@ -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<unknown> = new Set()): string | undefined => {
if (!value || depth > 6) return undefined;
@ -48,7 +56,7 @@ const extractDataUrl = (value: unknown, depth = 0, visited: Set<unknown> = 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;

View File

@ -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}`;

View File

@ -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<SavedSignature[]>(() => readFromStorage());