diff --git a/frontend/src/components/tools/sign/SignSettings.tsx b/frontend/src/components/tools/sign/SignSettings.tsx index ef6f3b907..9cf238b61 100644 --- a/frontend/src/components/tools/sign/SignSettings.tsx +++ b/frontend/src/components/tools/sign/SignSettings.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState } from 'react'; import { useTranslation } from "react-i18next"; -import { Stack, TextInput, FileInput, Paper, Group, Button, Text, Alert, Modal, ColorSwatch, Menu, ActionIcon, Slider, Select, Combobox, useCombobox, ColorPicker } from '@mantine/core'; +import { Stack, TextInput, FileInput, Paper, Group, Button, Text, Alert, Modal, ColorSwatch, Menu, ActionIcon, Slider, Select, Combobox, useCombobox, ColorPicker, Tabs } from '@mantine/core'; import ButtonSelector from "../../shared/ButtonSelector"; import { SignParameters } from "../../../hooks/tools/sign/useSignParameters"; @@ -12,9 +12,11 @@ interface SignSettingsProps { onActivateSignaturePlacement?: () => void; onDeactivateSignature?: () => void; onUpdateDrawSettings?: (color: string, size: number) => void; + onUndo?: () => void; + onRedo?: () => void; } -const SignSettings = ({ parameters, onParameterChange, disabled = false, onActivateDrawMode, onActivateSignaturePlacement, onDeactivateSignature, onUpdateDrawSettings }: SignSettingsProps) => { +const SignSettings = ({ parameters, onParameterChange, disabled = false, onActivateDrawMode, onActivateSignaturePlacement, onDeactivateSignature, onUpdateDrawSettings, onUndo, onRedo }: SignSettingsProps) => { const { t } = useTranslation(); const canvasRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); @@ -432,35 +434,43 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv return ( {/* Signature Type Selection */} -
- - {t('sign.type.title', 'Signature Type')} - - onParameterChange('signatureType', value as 'image' | 'text' | 'draw' | 'canvas')} - options={[ - { - value: 'draw', - label: t('sign.type.draw', 'Draw'), - }, - { - value: 'canvas', - label: t('sign.type.canvas', 'Canvas'), - }, - { - value: 'image', - label: t('sign.type.image', 'Image'), - }, - { - value: 'text', - label: t('sign.type.text', 'Text'), - }, - ]} - disabled={disabled} - /> -
+ onParameterChange('signatureType', value as 'image' | 'text' | 'draw' | 'canvas')} + > + + + {t('sign.type.draw', 'Draw')} + + + {t('sign.type.canvas', 'Canvas')} + + + {t('sign.type.image', 'Image')} + + + {t('sign.type.text', 'Text')} + + + + {/* Undo/Redo Controls */} + + + + {/* Signature Creation based on type */} {parameters.signatureType === 'canvas' && ( @@ -782,9 +792,9 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv {(parameters.signatureType === 'canvas' || parameters.signatureType === 'image' || parameters.signatureType === 'text') && ( - {parameters.signatureType === 'canvas' && 'Draw your signature in the canvas above. Placement mode will activate automatically when you finish drawing.'} - {parameters.signatureType === 'image' && 'Upload your signature image above. Placement mode will activate automatically when the image is loaded.'} - {parameters.signatureType === 'text' && 'Enter your name above. Placement mode will activate automatically when you type your name.'} + {parameters.signatureType === 'canvas' && 'After drawing your signature in the canvas above, click anywhere on the PDF to place it.'} + {parameters.signatureType === 'image' && 'After uploading your signature image above, click anywhere on the PDF to place it.'} + {parameters.signatureType === 'text' && 'After entering your name above, click anywhere on the PDF to place your signature.'} )} diff --git a/frontend/src/components/viewer/EmbedPdfViewer.tsx b/frontend/src/components/viewer/EmbedPdfViewer.tsx index 0d1a7f037..ac48b4daf 100644 --- a/frontend/src/components/viewer/EmbedPdfViewer.tsx +++ b/frontend/src/components/viewer/EmbedPdfViewer.tsx @@ -40,7 +40,7 @@ const EmbedPdfViewerContent = ({ const isSignatureMode = selectedTool === 'sign'; // Get signature context - const { signatureApiRef } = useSignature(); + const { signatureApiRef, historyApiRef } = useSignature(); // Get current file from FileContext @@ -189,8 +189,17 @@ const EmbedPdfViewerContent = ({ url={effectiveFile.url} enableSignature={isSignatureMode} signatureApiRef={signatureApiRef as React.RefObject} + historyApiRef={historyApiRef as React.RefObject} onSignatureAdded={(annotation) => { console.log('Signature added:', annotation); + if (annotation.type === 13) { + console.log('- imageSrc:', !!annotation.imageSrc, annotation.imageSrc?.length); + console.log('- contents:', !!annotation.contents, annotation.contents?.length); + console.log('- data:', !!annotation.data, annotation.data?.length); + console.log('- imageData:', !!annotation.imageData, annotation.imageData?.length); + console.log('- appearance:', !!annotation.appearance, typeof annotation.appearance); + console.log('- All keys:', Object.keys(annotation)); + } // Future: Handle signature completion }} /> diff --git a/frontend/src/components/viewer/HistoryAPIBridge.tsx b/frontend/src/components/viewer/HistoryAPIBridge.tsx new file mode 100644 index 000000000..a9454bfe0 --- /dev/null +++ b/frontend/src/components/viewer/HistoryAPIBridge.tsx @@ -0,0 +1,86 @@ +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'; + +export interface HistoryAPI { + undo: () => void; + redo: () => void; + canUndo: () => boolean; + canRedo: () => boolean; +} + +export interface HistoryAPIBridgeProps {} + +export const HistoryAPIBridge = forwardRef((props, ref) => { + const { provides: historyApi } = useHistoryCapability(); + const { provides: annotationApi } = useAnnotationCapability(); + const { getImageData } = useSignature(); + + + useImperativeHandle(ref, () => ({ + undo: () => { + if (historyApi) { + historyApi.undo(); + + // Restore image data for STAMP annotations after undo + setTimeout(() => { + if (!annotationApi) return; + + for (let pageIndex = 0; pageIndex < 10; pageIndex++) { + const pageAnnotationsTask = annotationApi.getPageAnnotations?.({ pageIndex }); + if (pageAnnotationsTask) { + pageAnnotationsTask.toPromise().then((pageAnnotations: any) => { + if (pageAnnotations) { + pageAnnotations.forEach((ann: any) => { + if (ann.type === 13) { + const storedImageData = getImageData(ann.id); + + if (storedImageData && (!ann.imageSrc || ann.imageSrc !== storedImageData)) { + const originalData = { + type: ann.type, + rect: ann.rect, + author: ann.author || 'Digital Signature', + subject: ann.subject || 'Digital Signature', + pageIndex: pageIndex, + id: ann.id, + created: ann.created || new Date(), + imageSrc: storedImageData + }; + + annotationApi.deleteAnnotation(pageIndex, ann.id); + setTimeout(() => { + annotationApi.createAnnotation(pageIndex, originalData); + }, 50); + } + } + }); + } + }).catch((error: any) => { + console.error(`Failed to get annotations for page ${pageIndex}:`, error); + }); + } + } + }, 200); + } + }, + + redo: () => { + if (historyApi) { + historyApi.redo(); + } + }, + + canUndo: () => { + return historyApi ? historyApi.canUndo() : false; + }, + + canRedo: () => { + return historyApi ? historyApi.canRedo() : false; + }, + }), [historyApi]); + + return null; // This is a bridge component with no UI +}); + +HistoryAPIBridge.displayName = 'HistoryAPIBridge'; \ No newline at end of file diff --git a/frontend/src/components/viewer/LocalEmbedPDF.tsx b/frontend/src/components/viewer/LocalEmbedPDF.tsx index 435519de5..89ee0e8b0 100644 --- a/frontend/src/components/viewer/LocalEmbedPDF.tsx +++ b/frontend/src/components/viewer/LocalEmbedPDF.tsx @@ -35,6 +35,7 @@ import { SearchAPIBridge } from './SearchAPIBridge'; import { ThumbnailAPIBridge } from './ThumbnailAPIBridge'; import { RotateAPIBridge } from './RotateAPIBridge'; import { SignatureAPIBridge, SignatureAPI } from './SignatureAPIBridge'; +import { HistoryAPIBridge, HistoryAPI } from './HistoryAPIBridge'; interface LocalEmbedPDFProps { file?: File | Blob; @@ -42,9 +43,10 @@ interface LocalEmbedPDFProps { enableSignature?: boolean; onSignatureAdded?: (annotation: any) => void; signatureApiRef?: React.RefObject; + historyApiRef?: React.RefObject; } -export function LocalEmbedPDF({ file, url, enableSignature = false, onSignatureAdded, signatureApiRef }: LocalEmbedPDFProps) { +export function LocalEmbedPDF({ file, url, enableSignature = false, onSignatureAdded, signatureApiRef, historyApiRef }: LocalEmbedPDFProps) { const [pdfUrl, setPdfUrl] = useState(null); const [annotations, setAnnotations] = useState>([]); @@ -257,6 +259,7 @@ export function LocalEmbedPDF({ file, url, enableSignature = false, onSignatureA {enableSignature && } + {enableSignature && } ((props, ref) => { const { provides: annotationApi } = useAnnotationCapability(); - const { signatureConfig } = useSignature(); + const { signatureConfig, storeImageData } = useSignature(); + // Enable keyboard deletion of selected annotations useEffect(() => { @@ -31,13 +32,30 @@ 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?.(); - if (id) { - annotationApi.deleteAnnotation(pageIndex, 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); + } + } + } + }, 10); } } }; @@ -50,7 +68,14 @@ export const SignatureAPIBridge = forwardRef { if (!annotationApi) return; - // Create image stamp annotation + // Create image stamp annotation with proper image data + console.log('Creating image annotation with data length:', signatureData?.length); + + const annotationId = uuidV4(); + + // Store image data in our persistent store + storeImageData(annotationId, signatureData); + annotationApi.createAnnotation(pageIndex, { type: PdfAnnotationSubtype.STAMP, rect: { @@ -60,8 +85,14 @@ export const SignatureAPIBridge = forwardRef void; activateDeleteMode: () => void; updateDrawSettings: (color: string, size: number) => void; + undo: () => void; + redo: () => void; + storeImageData: (id: string, data: string) => void; + getImageData: (id: string) => string | undefined; } // Combined context interface interface SignatureContextValue extends SignatureState, SignatureActions { signatureApiRef: React.RefObject; + historyApiRef: React.RefObject; } // Create context @@ -39,6 +45,8 @@ const initialState: SignatureState = { export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const [state, setState] = useState(initialState); const signatureApiRef = useRef(null); + const historyApiRef = useRef(null); + const imageDataStore = useRef>(new Map()); // Actions const setSignatureConfig = useCallback((config: SignParameters | null) => { @@ -100,12 +108,36 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children } }, []); + const undo = useCallback(() => { + if (historyApiRef.current) { + historyApiRef.current.undo(); + } + }, []); + + const redo = useCallback(() => { + if (historyApiRef.current) { + historyApiRef.current.redo(); + } + }, []); + + 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; + }, []); + // No auto-activation - all modes use manual buttons const contextValue: SignatureContextValue = { ...state, signatureApiRef, + historyApiRef, setSignatureConfig, setPlacementMode, activateDrawMode, @@ -113,6 +145,10 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children activateSignaturePlacementMode, activateDeleteMode, updateDrawSettings, + undo, + redo, + storeImageData, + getImageData, }; return ( diff --git a/frontend/src/tools/Sign.tsx b/frontend/src/tools/Sign.tsx index c5a6c3a46..0ea9f1ddf 100644 --- a/frontend/src/tools/Sign.tsx +++ b/frontend/src/tools/Sign.tsx @@ -12,7 +12,7 @@ import { useSignature } from "../contexts/SignatureContext"; const Sign = (props: BaseToolProps) => { const { t } = useTranslation(); const { setWorkbench } = useNavigation(); - const { setSignatureConfig, activateDrawMode, activateSignaturePlacementMode, deactivateDrawMode, updateDrawSettings } = useSignature(); + const { setSignatureConfig, activateDrawMode, activateSignaturePlacementMode, deactivateDrawMode, updateDrawSettings, undo, redo } = useSignature(); // Manual sync function const syncSignatureConfig = () => { @@ -65,6 +65,8 @@ const Sign = (props: BaseToolProps) => { onActivateSignaturePlacement={handleSignaturePlacement} onDeactivateSignature={deactivateDrawMode} onUpdateDrawSettings={updateDrawSettings} + onUndo={undo} + onRedo={redo} /> ), });