diff --git a/frontend/src/components/annotation/providers/PDFAnnotationProvider.tsx b/frontend/src/components/annotation/providers/PDFAnnotationProvider.tsx new file mode 100644 index 000000000..0979d59e3 --- /dev/null +++ b/frontend/src/components/annotation/providers/PDFAnnotationProvider.tsx @@ -0,0 +1,91 @@ +import React, { createContext, useContext, ReactNode } from 'react'; + +interface PDFAnnotationContextValue { + // Drawing mode management + activateDrawMode: () => void; + deactivateDrawMode: () => void; + activateSignaturePlacementMode: () => void; + activateDeleteMode: () => void; + + // Drawing settings + updateDrawSettings: (color: string, size: number) => void; + + // History operations + undo: () => void; + redo: () => void; + + // Image data management + storeImageData: (id: string, data: string) => void; + getImageData: (id: string) => string | undefined; + + // Placement state + isPlacementMode: boolean; + + // Signature configuration + signatureConfig: any | null; + setSignatureConfig: (config: any | null) => void; +} + +const PDFAnnotationContext = createContext(undefined); + +interface PDFAnnotationProviderProps { + children: ReactNode; + // These would come from the signature context + activateDrawMode: () => void; + deactivateDrawMode: () => void; + activateSignaturePlacementMode: () => void; + activateDeleteMode: () => void; + updateDrawSettings: (color: string, size: number) => void; + undo: () => void; + redo: () => void; + storeImageData: (id: string, data: string) => void; + getImageData: (id: string) => string | undefined; + isPlacementMode: boolean; + signatureConfig: any | null; + setSignatureConfig: (config: any | null) => void; +} + +export const PDFAnnotationProvider: React.FC = ({ + children, + activateDrawMode, + deactivateDrawMode, + activateSignaturePlacementMode, + activateDeleteMode, + updateDrawSettings, + undo, + redo, + storeImageData, + getImageData, + isPlacementMode, + signatureConfig, + setSignatureConfig +}) => { + const contextValue: PDFAnnotationContextValue = { + activateDrawMode, + deactivateDrawMode, + activateSignaturePlacementMode, + activateDeleteMode, + updateDrawSettings, + undo, + redo, + storeImageData, + getImageData, + isPlacementMode, + signatureConfig, + setSignatureConfig + }; + + return ( + + {children} + + ); +}; + +export const usePDFAnnotation = (): PDFAnnotationContextValue => { + const context = useContext(PDFAnnotationContext); + if (context === undefined) { + throw new Error('usePDFAnnotation must be used within a PDFAnnotationProvider'); + } + return context; +}; \ No newline at end of file diff --git a/frontend/src/components/annotation/shared/BaseAnnotationTool.tsx b/frontend/src/components/annotation/shared/BaseAnnotationTool.tsx new file mode 100644 index 000000000..6e203413c --- /dev/null +++ b/frontend/src/components/annotation/shared/BaseAnnotationTool.tsx @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import { Stack, Tabs, Alert, Text } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import { DrawingControls } from './DrawingControls'; +import { ColorPicker } from './ColorPicker'; +import { usePDFAnnotation } from '../providers/PDFAnnotationProvider'; + +export interface AnnotationToolConfig { + enableDrawing?: boolean; + enableImageUpload?: boolean; + enableTextInput?: boolean; + showPlaceButton?: boolean; + placeButtonText?: string; +} + +interface BaseAnnotationToolProps { + config: AnnotationToolConfig; + children: React.ReactNode; + onSignatureDataChange?: (data: string | null) => void; + disabled?: boolean; +} + +export const BaseAnnotationTool: React.FC = ({ + config, + children, + onSignatureDataChange, + disabled = false +}) => { + const { t } = useTranslation(); + const { + activateSignaturePlacementMode, + deactivateDrawMode, + undo, + redo, + isPlacementMode + } = usePDFAnnotation(); + + const [selectedColor, setSelectedColor] = useState('#000000'); + const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); + const [signatureData, setSignatureData] = useState(null); + + const handleSignatureDataChange = (data: string | null) => { + setSignatureData(data); + onSignatureDataChange?.(data); + }; + + const handlePlaceSignature = () => { + if (activateSignaturePlacementMode) { + activateSignaturePlacementMode(); + } + }; + + return ( + + {/* Drawing Controls (Undo/Redo/Place) */} + + + {/* Tool Content */} + {React.cloneElement(children as React.ReactElement, { + selectedColor, + signatureData, + onSignatureDataChange: handleSignatureDataChange, + onColorSwatchClick: () => setIsColorPickerOpen(true), + disabled + })} + + {/* Instructions for placing signature */} + + + Click anywhere on the PDF to place your annotation. + + + + {/* Color Picker Modal */} + setIsColorPickerOpen(false)} + selectedColor={selectedColor} + onColorChange={setSelectedColor} + /> + + ); +}; \ No newline at end of file diff --git a/frontend/src/components/annotation/shared/ColorPicker.tsx b/frontend/src/components/annotation/shared/ColorPicker.tsx new file mode 100644 index 000000000..40bb363b4 --- /dev/null +++ b/frontend/src/components/annotation/shared/ColorPicker.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Modal, Stack, ColorPicker as MantineColorPicker, Group, Button, ColorSwatch } from '@mantine/core'; + +interface ColorPickerProps { + isOpen: boolean; + onClose: () => void; + selectedColor: string; + onColorChange: (color: string) => void; + title?: string; +} + +export const ColorPicker: React.FC = ({ + isOpen, + onClose, + selectedColor, + onColorChange, + title = "Choose Color" +}) => { + return ( + + + + + + + + + ); +}; + +interface ColorSwatchButtonProps { + color: string; + onClick: () => void; + size?: number; +} + +export const ColorSwatchButton: React.FC = ({ + color, + onClick, + size = 24 +}) => { + return ( + + ); +}; \ No newline at end of file diff --git a/frontend/src/components/annotation/shared/DrawingCanvas.tsx b/frontend/src/components/annotation/shared/DrawingCanvas.tsx new file mode 100644 index 000000000..35b8eb70c --- /dev/null +++ b/frontend/src/components/annotation/shared/DrawingCanvas.tsx @@ -0,0 +1,443 @@ +import React, { useRef, useState, useCallback } from 'react'; +import { Paper, Group, Button, Modal, Stack, Text } from '@mantine/core'; +import { ColorSwatchButton } from './ColorPicker'; +import PenSizeSelector from '../../tools/sign/PenSizeSelector'; + +interface DrawingCanvasProps { + selectedColor: string; + penSize: number; + penSizeInput: string; + onColorSwatchClick: () => void; + onPenSizeChange: (size: number) => void; + onPenSizeInputChange: (input: string) => void; + onSignatureDataChange: (data: string | null) => void; + disabled?: boolean; + width?: number; + height?: number; + modalWidth?: number; + modalHeight?: number; + additionalButtons?: React.ReactNode; +} + +export const DrawingCanvas: React.FC = ({ + selectedColor, + penSize, + penSizeInput, + onColorSwatchClick, + onPenSizeChange, + onPenSizeInputChange, + onSignatureDataChange, + disabled = false, + width = 400, + height = 150, + modalWidth = 800, + modalHeight = 400, + additionalButtons +}) => { + const canvasRef = useRef(null); + const modalCanvasRef = useRef(null); + const visibleModalCanvasRef = useRef(null); + + const [isDrawing, setIsDrawing] = useState(false); + const [isModalDrawing, setIsModalDrawing] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const [signatureData, setSignatureData] = useState(null); + + // Drawing functions for main canvas + const startDrawing = useCallback((e: React.MouseEvent) => { + if (!canvasRef.current || disabled) return; + + setIsDrawing(true); + const rect = canvasRef.current.getBoundingClientRect(); + const scaleX = canvasRef.current.width / rect.width; + const scaleY = canvasRef.current.height / rect.height; + const x = (e.clientX - rect.left) * scaleX; + const y = (e.clientY - rect.top) * scaleY; + + const ctx = canvasRef.current.getContext('2d'); + if (ctx) { + ctx.strokeStyle = selectedColor; + ctx.lineWidth = penSize; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.beginPath(); + ctx.moveTo(x, y); + } + }, [disabled, selectedColor, penSize]); + + const draw = useCallback((e: React.MouseEvent) => { + if (!isDrawing || !canvasRef.current || disabled) return; + + const rect = canvasRef.current.getBoundingClientRect(); + const scaleX = canvasRef.current.width / rect.width; + const scaleY = canvasRef.current.height / rect.height; + const x = (e.clientX - rect.left) * scaleX; + const y = (e.clientY - rect.top) * scaleY; + + const ctx = canvasRef.current.getContext('2d'); + if (ctx) { + ctx.lineTo(x, y); + ctx.stroke(); + } + }, [isDrawing, disabled]); + + const stopDrawing = useCallback(() => { + if (!isDrawing || disabled) return; + + setIsDrawing(false); + + // Save canvas as signature data + if (canvasRef.current) { + const dataURL = canvasRef.current.toDataURL('image/png'); + setSignatureData(dataURL); + onSignatureDataChange(dataURL); + } + }, [isDrawing, disabled, onSignatureDataChange]); + + // Modal canvas drawing functions + const startModalDrawing = useCallback((e: React.MouseEvent) => { + if (!visibleModalCanvasRef.current || !modalCanvasRef.current) return; + + setIsModalDrawing(true); + const rect = visibleModalCanvasRef.current.getBoundingClientRect(); + const scaleX = visibleModalCanvasRef.current.width / rect.width; + const scaleY = visibleModalCanvasRef.current.height / rect.height; + const x = (e.clientX - rect.left) * scaleX; + const y = (e.clientY - rect.top) * scaleY; + + // Draw on both the visible modal canvas and hidden canvas + const visibleCtx = visibleModalCanvasRef.current.getContext('2d'); + const hiddenCtx = modalCanvasRef.current.getContext('2d'); + + [visibleCtx, hiddenCtx].forEach(ctx => { + if (ctx) { + ctx.strokeStyle = selectedColor; + ctx.lineWidth = penSize; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.beginPath(); + ctx.moveTo(x, y); + } + }); + }, [selectedColor, penSize]); + + const drawModal = useCallback((e: React.MouseEvent) => { + if (!isModalDrawing || !visibleModalCanvasRef.current || !modalCanvasRef.current) return; + + const rect = visibleModalCanvasRef.current.getBoundingClientRect(); + const scaleX = visibleModalCanvasRef.current.width / rect.width; + const scaleY = visibleModalCanvasRef.current.height / rect.height; + const x = (e.clientX - rect.left) * scaleX; + const y = (e.clientY - rect.top) * scaleY; + + // Draw on both canvases + const visibleCtx = visibleModalCanvasRef.current.getContext('2d'); + const hiddenCtx = modalCanvasRef.current.getContext('2d'); + + [visibleCtx, hiddenCtx].forEach(ctx => { + if (ctx) { + ctx.lineTo(x, y); + ctx.stroke(); + } + }); + }, [isModalDrawing]); + + const stopModalDrawing = useCallback(() => { + if (!isModalDrawing) return; + setIsModalDrawing(false); + + // Sync the canvases and update signature data (only when drawing stops) + if (modalCanvasRef.current) { + const dataURL = modalCanvasRef.current.toDataURL('image/png'); + setSignatureData(dataURL); + onSignatureDataChange(dataURL); + + // Also update the small canvas display + if (canvasRef.current) { + const smallCtx = canvasRef.current.getContext('2d'); + if (smallCtx) { + const img = new Image(); + img.onload = () => { + smallCtx.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height); + smallCtx.drawImage(img, 0, 0, canvasRef.current!.width, canvasRef.current!.height); + }; + img.src = dataURL; + } + } + } + }, [isModalDrawing]); + + // Clear canvas functions + const clearCanvas = useCallback(() => { + if (!canvasRef.current || disabled) return; + + const ctx = canvasRef.current.getContext('2d'); + if (ctx) { + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + + // Also clear the modal canvas if it exists + if (modalCanvasRef.current) { + const modalCtx = modalCanvasRef.current.getContext('2d'); + if (modalCtx) { + modalCtx.clearRect(0, 0, modalCanvasRef.current.width, modalCanvasRef.current.height); + } + } + + setSignatureData(null); + onSignatureDataChange(null); + } + }, [disabled]); + + const clearModalCanvas = useCallback(() => { + // Clear both modal canvases (visible and hidden) + if (modalCanvasRef.current) { + const hiddenCtx = modalCanvasRef.current.getContext('2d'); + if (hiddenCtx) { + hiddenCtx.clearRect(0, 0, modalCanvasRef.current.width, modalCanvasRef.current.height); + } + } + + if (visibleModalCanvasRef.current) { + const visibleCtx = visibleModalCanvasRef.current.getContext('2d'); + if (visibleCtx) { + visibleCtx.clearRect(0, 0, visibleModalCanvasRef.current.width, visibleModalCanvasRef.current.height); + } + } + + // Also clear the main canvas and signature data + if (canvasRef.current) { + const mainCtx = canvasRef.current.getContext('2d'); + if (mainCtx) { + mainCtx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + } + } + + setSignatureData(null); + onSignatureDataChange(null); + }, []); + + const saveModalSignature = useCallback(() => { + if (!modalCanvasRef.current) return; + + const dataURL = modalCanvasRef.current.toDataURL('image/png'); + setSignatureData(dataURL); + onSignatureDataChange(dataURL); + + // Copy to small canvas for display + if (canvasRef.current) { + const ctx = canvasRef.current.getContext('2d'); + if (ctx) { + const img = new Image(); + img.onload = () => { + ctx.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height); + ctx.drawImage(img, 0, 0, canvasRef.current!.width, canvasRef.current!.height); + }; + img.src = dataURL; + } + } + + setIsModalOpen(false); + }, []); + + const openModal = useCallback(() => { + setIsModalOpen(true); + // Copy content to modal canvas after a brief delay + setTimeout(() => { + if (visibleModalCanvasRef.current && modalCanvasRef.current) { + const visibleCtx = visibleModalCanvasRef.current.getContext('2d'); + if (visibleCtx) { + visibleCtx.strokeStyle = selectedColor; + visibleCtx.lineWidth = penSize; + visibleCtx.lineCap = 'round'; + visibleCtx.lineJoin = 'round'; + visibleCtx.clearRect(0, 0, visibleModalCanvasRef.current.width, visibleModalCanvasRef.current.height); + visibleCtx.drawImage(modalCanvasRef.current, 0, 0, visibleModalCanvasRef.current.width, visibleModalCanvasRef.current.height); + } + } + }, 300); + }, [selectedColor, penSize]); + + // Initialize canvas settings whenever color or pen size changes + React.useEffect(() => { + const updateCanvas = (canvas: HTMLCanvasElement | null) => { + if (!canvas) return; + const ctx = canvas.getContext('2d'); + if (ctx) { + ctx.strokeStyle = selectedColor; + ctx.lineWidth = penSize; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + } + }; + + updateCanvas(canvasRef.current); + updateCanvas(modalCanvasRef.current); + updateCanvas(visibleModalCanvasRef.current); + }, [selectedColor, penSize]); + + return ( + <> + + + + Draw your signature + +
+ Color + + + +
+
+ Pen Size + +
+
+ +
+
+
+ + +
+ {additionalButtons} +
+ +
+
+
+ + {/* Hidden canvas for modal synchronization */} + + + {/* Modal for larger signature canvas */} + setIsModalOpen(false)} + title="Draw Your Signature" + size="xl" + centered + > + + {/* Color and Pen Size picker */} + + +
+ Color + +
+
+ Pen Size + +
+
+
+ + + + + + + + + + + + +
+
+ + ); +}; + +export default DrawingCanvas; \ No newline at end of file diff --git a/frontend/src/components/annotation/shared/DrawingControls.tsx b/frontend/src/components/annotation/shared/DrawingControls.tsx new file mode 100644 index 000000000..62c7c615f --- /dev/null +++ b/frontend/src/components/annotation/shared/DrawingControls.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { Group, Button } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; + +interface DrawingControlsProps { + onUndo?: () => void; + onRedo?: () => void; + onPlaceSignature?: () => void; + hasSignatureData?: boolean; + disabled?: boolean; + showPlaceButton?: boolean; + placeButtonText?: string; +} + +export const DrawingControls: React.FC = ({ + onUndo, + onRedo, + onPlaceSignature, + hasSignatureData = false, + disabled = false, + showPlaceButton = true, + placeButtonText = "Update and Place" +}) => { + const { t } = useTranslation(); + + return ( + + {/* Undo/Redo Controls */} + + + + {/* Place Signature Button */} + {showPlaceButton && onPlaceSignature && ( + + )} + + ); +}; \ No newline at end of file diff --git a/frontend/src/components/annotation/shared/ImageUploader.tsx b/frontend/src/components/annotation/shared/ImageUploader.tsx new file mode 100644 index 000000000..d590a7bc1 --- /dev/null +++ b/frontend/src/components/annotation/shared/ImageUploader.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { FileInput, Text, Stack } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; + +interface ImageUploaderProps { + onImageChange: (file: File | null) => void; + disabled?: boolean; + label?: string; + placeholder?: string; + hint?: string; +} + +export const ImageUploader: React.FC = ({ + onImageChange, + disabled = false, + label, + placeholder, + hint +}) => { + const { t } = useTranslation(); + + const handleImageChange = async (file: File | null) => { + if (file && !disabled) { + try { + // Validate that it's actually an image file + if (!file.type.startsWith('image/')) { + console.error('Selected file is not an image'); + return; + } + + onImageChange(file); + } catch (error) { + console.error('Error processing image file:', error); + } + } else if (!file) { + // Clear image data when no file is selected + onImageChange(null); + } + }; + + return ( + + + + {hint || t('sign.image.hint', 'Upload a PNG or JPG image of your signature')} + + + ); +}; \ No newline at end of file diff --git a/frontend/src/components/annotation/shared/TextInputWithFont.tsx b/frontend/src/components/annotation/shared/TextInputWithFont.tsx new file mode 100644 index 000000000..b85511cd5 --- /dev/null +++ b/frontend/src/components/annotation/shared/TextInputWithFont.tsx @@ -0,0 +1,126 @@ +import React, { useState, useEffect } from 'react'; +import { Stack, TextInput, Select, Combobox, useCombobox } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; + +interface TextInputWithFontProps { + text: string; + onTextChange: (text: string) => void; + fontSize: number; + onFontSizeChange: (size: number) => void; + fontFamily: string; + onFontFamilyChange: (family: string) => void; + disabled?: boolean; + label?: string; + placeholder?: string; +} + +export const TextInputWithFont: React.FC = ({ + text, + onTextChange, + fontSize, + onFontSizeChange, + fontFamily, + onFontFamilyChange, + disabled = false, + label, + placeholder +}) => { + const { t } = useTranslation(); + const [fontSizeInput, setFontSizeInput] = useState(fontSize.toString()); + const fontSizeCombobox = useCombobox(); + + // Sync font size input with prop changes + useEffect(() => { + setFontSizeInput(fontSize.toString()); + }, [fontSize]); + + const fontOptions = [ + { value: 'Helvetica', label: 'Helvetica' }, + { value: 'Times-Roman', label: 'Times' }, + { value: 'Courier', label: 'Courier' }, + { value: 'Arial', label: 'Arial' }, + { value: 'Georgia', label: 'Georgia' }, + ]; + + const fontSizeOptions = ['8', '12', '16', '20', '24', '28', '32', '36', '40', '48']; + + return ( + + onTextChange(e.target.value)} + disabled={disabled} + required + /> + + {/* Font Selection */} + onParameterChange('fontFamily', value || 'Helvetica')} - data={[ - { value: 'Helvetica', label: 'Helvetica' }, - { value: 'Times-Roman', label: 'Times' }, - { value: 'Courier', label: 'Courier' }, - { value: 'Arial', label: 'Arial' }, - { value: 'Georgia', label: 'Georgia' }, - ]} - disabled={disabled} - searchable - allowDeselect={false} - /> - - {/* Font Size */} - { - setFontSizeInput(optionValue); - const size = parseInt(optionValue); - if (!isNaN(size)) { - onParameterChange('fontSize', size); - } - fontSizeCombobox.closeDropdown(); - }} - store={fontSizeCombobox} - withinPortal={false} - > - - { - const value = event.currentTarget.value; - setFontSizeInput(value); - - // Parse and validate the typed value in real-time - const size = parseInt(value); - if (!isNaN(size) && size >= 8 && size <= 72) { - onParameterChange('fontSize', size); - } - - fontSizeCombobox.openDropdown(); - fontSizeCombobox.updateSelectedOptionIndex(); - }} - onClick={() => fontSizeCombobox.openDropdown()} - onFocus={() => fontSizeCombobox.openDropdown()} - onBlur={() => { - fontSizeCombobox.closeDropdown(); - // Clean up invalid values on blur - const size = parseInt(fontSizeInput); - if (isNaN(size) || size < 8 || size > 72) { - setFontSizeInput((parameters.fontSize || 16).toString()); - } - }} - disabled={disabled} - /> - - - - - {['8', '12', '16', '20', '24', '28', '32', '36', '40', '48'].map((size) => ( - - {size}px - - ))} - - - - + onParameterChange('signerName', text)} + fontSize={parameters.fontSize || 16} + onFontSizeChange={(size) => onParameterChange('fontSize', size)} + fontFamily={parameters.fontFamily || 'Helvetica'} + onFontFamilyChange={(family) => onParameterChange('fontFamily', family)} + disabled={disabled} + /> )} {/* Direct PDF Drawing */} @@ -692,23 +244,37 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv {/* Drawing Controls */} {/* Color Picker */} - setIsColorPickerOpen(true)} - /> +
+ Color +
setIsColorPickerOpen(true)} + /> +
{/* Pen Size */}
Pen Size - { + const size = parseInt(e.target.value); + if (!isNaN(size) && size >= 1 && size <= 200) { + setPenSize(size); + } + }} + min={1} + max={200} disabled={disabled} + style={{ width: '100%', padding: '4px 8px' }} />
@@ -716,134 +282,24 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv )} - {/* Instructions for placing signature */} {(parameters.signatureType === 'canvas' || parameters.signatureType === 'image' || parameters.signatureType === 'text') && ( - {parameters.signatureType === 'canvas' && 'After drawing your signature in the canvas above, click anywhere on the PDF to place it.'} + {parameters.signatureType === 'canvas' && 'After drawing your signature in the canvas above, click "Update and Place" then 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.'} )} - {/* Hidden canvas for modal synchronization - always exists */} - - - {/* Modal for larger signature canvas */} - setIsModalOpen(false)} - title="Draw Your Signature" - size="xl" - centered - > - - {/* Color and Pen Size picker */} - - -
- Color - setIsColorPickerOpen(true)} - /> -
-
- Pen Size - -
-
-
- - - - - - - - - - - - -
-
- {/* Color Picker Modal */} - setIsColorPickerOpen(false)} - title="Choose Color" - size="sm" - centered - > - - - - - - - + selectedColor={selectedColor} + onColorChange={setSelectedColor} + /> {/* Save Button */} {onSave && ( @@ -863,4 +319,4 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv ); }; -export default SignSettings; +export default SignSettings; \ No newline at end of file diff --git a/frontend/src/contexts/SignatureContext.tsx b/frontend/src/contexts/SignatureContext.tsx index 517eb5bce..cdde395b7 100644 --- a/frontend/src/contexts/SignatureContext.tsx +++ b/frontend/src/contexts/SignatureContext.tsx @@ -82,7 +82,7 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children signatureApiRef.current.activateSignaturePlacementMode(); setPlacementMode(true); } - }, [state.signatureConfig, setPlacementMode]); + }, [setPlacementMode]); const activateDeleteMode = useCallback(() => { if (signatureApiRef.current) { diff --git a/frontend/src/tools/Sign.tsx b/frontend/src/tools/Sign.tsx index 0d8e90351..da4a23d75 100644 --- a/frontend/src/tools/Sign.tsx +++ b/frontend/src/tools/Sign.tsx @@ -25,17 +25,9 @@ const Sign = (props: BaseToolProps) => { // Track which signature mode was active for reactivation after save const activeModeRef = useRef<'draw' | 'placement' | null>(null); - // Manual sync function - const syncSignatureConfig = () => { - setSignatureConfig(base.params.parameters); - }; - - // Single handler that syncs first + // Single handler that activates placement mode const handleSignaturePlacement = () => { - syncSignatureConfig(); - setTimeout(() => { - activateSignaturePlacementMode(); - }, 100); + activateSignaturePlacementMode(); }; const base = useBaseTool(