diff --git a/frontend/src/components/viewer/HistoryAPIBridge.tsx b/frontend/src/components/viewer/HistoryAPIBridge.tsx index a9454bfe0..8a251a193 100644 --- a/frontend/src/components/viewer/HistoryAPIBridge.tsx +++ b/frontend/src/components/viewer/HistoryAPIBridge.tsx @@ -2,6 +2,7 @@ import React, { useImperativeHandle, forwardRef, useEffect } from 'react'; import { useHistoryCapability } from '@embedpdf/plugin-history/react'; import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react'; import { useSignature } from '../../contexts/SignatureContext'; +import { uuidV4 } from '@embedpdf/models'; export interface HistoryAPI { undo: () => void; @@ -15,7 +16,80 @@ export interface HistoryAPIBridgeProps {} export const HistoryAPIBridge = forwardRef((props, ref) => { const { provides: historyApi } = useHistoryCapability(); const { provides: annotationApi } = useAnnotationCapability(); - const { getImageData } = useSignature(); + const { getImageData, storeImageData } = useSignature(); + + // Monitor annotation events to detect when annotations are restored + useEffect(() => { + if (!annotationApi) return; + + const handleAnnotationEvent = (event: any) => { + const annotation = event.annotation; + + // Store image data for all STAMP annotations immediately when created or modified + if (annotation && annotation.type === 13 && annotation.id && annotation.imageSrc) { + const storedImageData = getImageData(annotation.id); + if (!storedImageData || storedImageData !== annotation.imageSrc) { + console.log('HistoryAPI: Storing image data for annotation', annotation.id); + storeImageData(annotation.id, annotation.imageSrc); + } + } + + // Handle annotation restoration after undo operations + if (event.type === 'create' && event.committed) { + // Check if this is a STAMP annotation (signature) that might need image data restoration + if (annotation && annotation.type === 13 && annotation.id) { + const storedImageData = getImageData(annotation.id); + + // Delay the check to allow the annotation to be fully created + setTimeout(() => { + const currentStoredData = getImageData(annotation.id); + // Check if the annotation lacks image data but we have it stored + if (currentStoredData && (!annotation.imageSrc || annotation.imageSrc !== currentStoredData)) { + console.log('HistoryAPI: Restoring image data for annotation', annotation.id); + + // Generate new ID to avoid React key conflicts + const newId = uuidV4(); + + // Recreation with stored image data + const restoredData = { + type: annotation.type, + rect: annotation.rect, + author: annotation.author || 'Digital Signature', + subject: annotation.subject || 'Digital Signature', + pageIndex: event.pageIndex, + id: newId, + created: annotation.created || new Date(), + imageSrc: currentStoredData + }; + + // Update stored data to use new ID + storeImageData(newId, currentStoredData); + + // Replace the annotation with one that has proper image data + try { + annotationApi.deleteAnnotation(event.pageIndex, annotation.id); + // Small delay to ensure deletion completes + setTimeout(() => { + annotationApi.createAnnotation(event.pageIndex, restoredData); + }, 50); + } catch (error) { + console.error('HistoryAPI: Failed to restore annotation:', error); + } + } + }, 100); + } + } + }; + + // Add the event listener + annotationApi.onAnnotationEvent(handleAnnotationEvent); + + // Cleanup function + return () => { + // Note: EmbedPDF doesn't provide a way to remove event listeners + // This is a limitation of the current API + }; + }, [annotationApi, getImageData, storeImageData]); useImperativeHandle(ref, () => ({ @@ -24,30 +98,43 @@ export const HistoryAPIBridge = forwardRef((p historyApi.undo(); // Restore image data for STAMP annotations after undo + // This handles both manual undo and delete+undo scenarios setTimeout(() => { if (!annotationApi) return; - for (let pageIndex = 0; pageIndex < 10; pageIndex++) { + // Check reasonable number of pages - most documents have fewer than 10 pages + for (let pageIndex = 0; pageIndex < 5; pageIndex++) { const pageAnnotationsTask = annotationApi.getPageAnnotations?.({ pageIndex }); if (pageAnnotationsTask) { pageAnnotationsTask.toPromise().then((pageAnnotations: any) => { - if (pageAnnotations) { + if (pageAnnotations && pageAnnotations.length > 0) { pageAnnotations.forEach((ann: any) => { - if (ann.type === 13) { + if (ann.type === 13) { // STAMP annotations const storedImageData = getImageData(ann.id); if (storedImageData && (!ann.imageSrc || ann.imageSrc !== storedImageData)) { + // Generate new ID to avoid React key conflicts + const newId = uuidV4(); + const originalData = { type: ann.type, rect: ann.rect, author: ann.author || 'Digital Signature', subject: ann.subject || 'Digital Signature', pageIndex: pageIndex, - id: ann.id, + id: newId, created: ann.created || new Date(), - imageSrc: storedImageData + imageSrc: storedImageData, + // Store in multiple fields to ensure compatibility + contents: storedImageData, + data: storedImageData, + imageData: storedImageData, + appearance: storedImageData }; + // Update stored data to use new ID + storeImageData(newId, storedImageData); + annotationApi.deleteAnnotation(pageIndex, ann.id); setTimeout(() => { annotationApi.createAnnotation(pageIndex, originalData); @@ -56,8 +143,8 @@ export const HistoryAPIBridge = forwardRef((p } }); } - }).catch((error: any) => { - console.error(`Failed to get annotations for page ${pageIndex}:`, error); + }).catch(() => { + // Silently ignore "Page not found" errors for non-existent pages }); } } diff --git a/frontend/src/components/viewer/SignatureAPIBridge.tsx b/frontend/src/components/viewer/SignatureAPIBridge.tsx index 586bf7777..1991c5bee 100644 --- a/frontend/src/components/viewer/SignatureAPIBridge.tsx +++ b/frontend/src/components/viewer/SignatureAPIBridge.tsx @@ -32,37 +32,41 @@ export const SignatureAPIBridge = forwardRef { - // Check if the annotation is still selected after a brief delay - // If EmbedPDF handled it natively, it should be gone - const stillSelected = annotationApi.getSelectedAnnotation?.(); + const annotation = selectedAnnotation as any; + const pageIndex = annotation.object?.pageIndex || 0; + const id = annotation.object?.id; - if (stillSelected) { - // EmbedPDF didn't handle it, so we need to delete manually - const annotation = stillSelected as any; - const pageIndex = annotation.object?.pageIndex || 0; - const id = annotation.object?.id; - - if (id) { - // Try deleteSelected method first (should integrate with history) - if ((annotationApi as any).deleteSelected) { - (annotationApi as any).deleteSelected(); - } else { - // Fallback to direct deletion - annotationApi.deleteAnnotation(pageIndex, id); + // For STAMP annotations, ensure image data is preserved before deletion + if (annotation.object?.type === 13 && id) { + // Get current annotation data to ensure we have latest image data stored + const pageAnnotationsTask = annotationApi.getPageAnnotations?.({ pageIndex }); + if (pageAnnotationsTask) { + pageAnnotationsTask.toPromise().then((pageAnnotations: any) => { + const currentAnn = pageAnnotations?.find((ann: any) => ann.id === id); + if (currentAnn && currentAnn.imageSrc) { + // Ensure the image data is stored in our persistent store + storeImageData(id, currentAnn.imageSrc); } - } + }).catch(console.error); } - }, 10); + } + + // Use EmbedPDF's native deletion which should integrate with history + if ((annotationApi as any).deleteSelected) { + (annotationApi as any).deleteSelected(); + } else { + // Fallback to direct deletion - less ideal for history + if (id) { + annotationApi.deleteAnnotation(pageIndex, id); + } + } } } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); - }, [annotationApi]); + }, [annotationApi, storeImageData]); useImperativeHandle(ref, () => ({ addImageSignature: (signatureData: string, x: number, y: number, width: number, height: number, pageIndex: number) => { @@ -253,6 +257,19 @@ export const SignatureAPIBridge = forwardRef { if (!annotationApi) return; + + // Before deleting, try to preserve image data for potential undo + const pageAnnotationsTask = annotationApi.getPageAnnotations?.({ pageIndex }); + if (pageAnnotationsTask) { + pageAnnotationsTask.toPromise().then((pageAnnotations: any) => { + const annotation = pageAnnotations?.find((ann: any) => ann.id === annotationId); + if (annotation && annotation.type === 13 && annotation.imageSrc) { + // Store image data before deletion + storeImageData(annotationId, annotation.imageSrc); + } + }).catch(console.error); + } + // Delete specific annotation by ID annotationApi.deleteAnnotation(pageIndex, annotationId); }, diff --git a/frontend/src/contexts/SignatureContext.tsx b/frontend/src/contexts/SignatureContext.tsx index 52019d07c..83a2097c5 100644 --- a/frontend/src/contexts/SignatureContext.tsx +++ b/frontend/src/contexts/SignatureContext.tsx @@ -121,14 +121,11 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children }, []); const storeImageData = useCallback((id: string, data: string) => { - console.log('Storing image data for annotation:', id, data.length, 'chars'); imageDataStore.current.set(id, data); }, []); const getImageData = useCallback((id: string) => { - const data = imageDataStore.current.get(id); - console.log('Retrieving image data for annotation:', id, data?.length, 'chars'); - return data; + return imageDataStore.current.get(id); }, []);