mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
history tweaks
This commit is contained in:
parent
3d2607f72a
commit
a8a0808274
@ -2,6 +2,7 @@ import React, { useImperativeHandle, forwardRef, useEffect } from 'react';
|
|||||||
import { useHistoryCapability } from '@embedpdf/plugin-history/react';
|
import { useHistoryCapability } from '@embedpdf/plugin-history/react';
|
||||||
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
||||||
import { useSignature } from '../../contexts/SignatureContext';
|
import { useSignature } from '../../contexts/SignatureContext';
|
||||||
|
import { uuidV4 } from '@embedpdf/models';
|
||||||
|
|
||||||
export interface HistoryAPI {
|
export interface HistoryAPI {
|
||||||
undo: () => void;
|
undo: () => void;
|
||||||
@ -15,7 +16,80 @@ export interface HistoryAPIBridgeProps {}
|
|||||||
export const HistoryAPIBridge = forwardRef<HistoryAPI, HistoryAPIBridgeProps>((props, ref) => {
|
export const HistoryAPIBridge = forwardRef<HistoryAPI, HistoryAPIBridgeProps>((props, ref) => {
|
||||||
const { provides: historyApi } = useHistoryCapability();
|
const { provides: historyApi } = useHistoryCapability();
|
||||||
const { provides: annotationApi } = useAnnotationCapability();
|
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, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@ -24,30 +98,43 @@ export const HistoryAPIBridge = forwardRef<HistoryAPI, HistoryAPIBridgeProps>((p
|
|||||||
historyApi.undo();
|
historyApi.undo();
|
||||||
|
|
||||||
// Restore image data for STAMP annotations after undo
|
// Restore image data for STAMP annotations after undo
|
||||||
|
// This handles both manual undo and delete+undo scenarios
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!annotationApi) return;
|
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 });
|
const pageAnnotationsTask = annotationApi.getPageAnnotations?.({ pageIndex });
|
||||||
if (pageAnnotationsTask) {
|
if (pageAnnotationsTask) {
|
||||||
pageAnnotationsTask.toPromise().then((pageAnnotations: any) => {
|
pageAnnotationsTask.toPromise().then((pageAnnotations: any) => {
|
||||||
if (pageAnnotations) {
|
if (pageAnnotations && pageAnnotations.length > 0) {
|
||||||
pageAnnotations.forEach((ann: any) => {
|
pageAnnotations.forEach((ann: any) => {
|
||||||
if (ann.type === 13) {
|
if (ann.type === 13) { // STAMP annotations
|
||||||
const storedImageData = getImageData(ann.id);
|
const storedImageData = getImageData(ann.id);
|
||||||
|
|
||||||
if (storedImageData && (!ann.imageSrc || ann.imageSrc !== storedImageData)) {
|
if (storedImageData && (!ann.imageSrc || ann.imageSrc !== storedImageData)) {
|
||||||
|
// Generate new ID to avoid React key conflicts
|
||||||
|
const newId = uuidV4();
|
||||||
|
|
||||||
const originalData = {
|
const originalData = {
|
||||||
type: ann.type,
|
type: ann.type,
|
||||||
rect: ann.rect,
|
rect: ann.rect,
|
||||||
author: ann.author || 'Digital Signature',
|
author: ann.author || 'Digital Signature',
|
||||||
subject: ann.subject || 'Digital Signature',
|
subject: ann.subject || 'Digital Signature',
|
||||||
pageIndex: pageIndex,
|
pageIndex: pageIndex,
|
||||||
id: ann.id,
|
id: newId,
|
||||||
created: ann.created || new Date(),
|
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);
|
annotationApi.deleteAnnotation(pageIndex, ann.id);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
annotationApi.createAnnotation(pageIndex, originalData);
|
annotationApi.createAnnotation(pageIndex, originalData);
|
||||||
@ -56,8 +143,8 @@ export const HistoryAPIBridge = forwardRef<HistoryAPI, HistoryAPIBridgeProps>((p
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch((error: any) => {
|
}).catch(() => {
|
||||||
console.error(`Failed to get annotations for page ${pageIndex}:`, error);
|
// Silently ignore "Page not found" errors for non-existent pages
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,37 +32,41 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI, SignatureAPIBridgePro
|
|||||||
const selectedAnnotation = annotationApi.getSelectedAnnotation?.();
|
const selectedAnnotation = annotationApi.getSelectedAnnotation?.();
|
||||||
|
|
||||||
if (selectedAnnotation) {
|
if (selectedAnnotation) {
|
||||||
// First, let EmbedPDF try to handle this natively
|
const annotation = selectedAnnotation as any;
|
||||||
// Only intervene if needed
|
|
||||||
setTimeout(() => {
|
|
||||||
// Check if the annotation is still selected after a brief delay
|
|
||||||
// If EmbedPDF handled it natively, it should be gone
|
|
||||||
const stillSelected = annotationApi.getSelectedAnnotation?.();
|
|
||||||
|
|
||||||
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 pageIndex = annotation.object?.pageIndex || 0;
|
||||||
const id = annotation.object?.id;
|
const id = annotation.object?.id;
|
||||||
|
|
||||||
if (id) {
|
// For STAMP annotations, ensure image data is preserved before deletion
|
||||||
// Try deleteSelected method first (should integrate with history)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use EmbedPDF's native deletion which should integrate with history
|
||||||
if ((annotationApi as any).deleteSelected) {
|
if ((annotationApi as any).deleteSelected) {
|
||||||
(annotationApi as any).deleteSelected();
|
(annotationApi as any).deleteSelected();
|
||||||
} else {
|
} else {
|
||||||
// Fallback to direct deletion
|
// Fallback to direct deletion - less ideal for history
|
||||||
|
if (id) {
|
||||||
annotationApi.deleteAnnotation(pageIndex, id);
|
annotationApi.deleteAnnotation(pageIndex, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('keydown', handleKeyDown);
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [annotationApi]);
|
}, [annotationApi, storeImageData]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
addImageSignature: (signatureData: string, x: number, y: number, width: number, height: number, pageIndex: number) => {
|
addImageSignature: (signatureData: string, x: number, y: number, width: number, height: number, pageIndex: number) => {
|
||||||
@ -253,6 +257,19 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI, SignatureAPIBridgePro
|
|||||||
|
|
||||||
deleteAnnotation: (annotationId: string, pageIndex: number) => {
|
deleteAnnotation: (annotationId: string, pageIndex: number) => {
|
||||||
if (!annotationApi) return;
|
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
|
// Delete specific annotation by ID
|
||||||
annotationApi.deleteAnnotation(pageIndex, annotationId);
|
annotationApi.deleteAnnotation(pageIndex, annotationId);
|
||||||
},
|
},
|
||||||
|
@ -121,14 +121,11 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const storeImageData = useCallback((id: string, data: string) => {
|
const storeImageData = useCallback((id: string, data: string) => {
|
||||||
console.log('Storing image data for annotation:', id, data.length, 'chars');
|
|
||||||
imageDataStore.current.set(id, data);
|
imageDataStore.current.set(id, data);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getImageData = useCallback((id: string) => {
|
const getImageData = useCallback((id: string) => {
|
||||||
const data = imageDataStore.current.get(id);
|
return imageDataStore.current.get(id);
|
||||||
console.log('Retrieving image data for annotation:', id, data?.length, 'chars');
|
|
||||||
return data;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user