diff --git a/frontend/src/core/components/annotation/shared/BaseAnnotationTool.tsx b/frontend/src/core/components/annotation/shared/BaseAnnotationTool.tsx index c61b61cfd..ea093b5be 100644 --- a/frontend/src/core/components/annotation/shared/BaseAnnotationTool.tsx +++ b/frontend/src/core/components/annotation/shared/BaseAnnotationTool.tsx @@ -1,9 +1,10 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Stack, Alert, Text } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { DrawingControls } from '@app/components/annotation/shared/DrawingControls'; import { ColorPicker } from '@app/components/annotation/shared/ColorPicker'; import { usePDFAnnotation } from '@app/components/annotation/providers/PDFAnnotationProvider'; +import { useSignature } from '@app/contexts/SignatureContext'; export interface AnnotationToolConfig { enableDrawing?: boolean; @@ -32,10 +33,34 @@ export const BaseAnnotationTool: React.FC = ({ undo, redo } = usePDFAnnotation(); + const { historyApiRef } = useSignature(); const [selectedColor, setSelectedColor] = useState('#000000'); const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); const [signatureData, setSignatureData] = useState(null); + const [historyAvailability, setHistoryAvailability] = useState({ canUndo: false, canRedo: false }); + const historyApiInstance = historyApiRef.current; + + useEffect(() => { + if (!historyApiInstance) { + setHistoryAvailability({ canUndo: false, canRedo: false }); + return; + } + + const updateAvailability = () => { + setHistoryAvailability({ + canUndo: historyApiInstance.canUndo?.() ?? false, + canRedo: historyApiInstance.canRedo?.() ?? false, + }); + }; + + const unsubscribe = historyApiInstance.subscribe?.(updateAvailability); + updateAvailability(); + + return () => { + unsubscribe?.(); + }; + }, [historyApiInstance]); const handleSignatureDataChange = (data: string | null) => { setSignatureData(data); @@ -54,6 +79,8 @@ export const BaseAnnotationTool: React.FC = ({ = ({ /> ); -}; \ No newline at end of file +}; diff --git a/frontend/src/core/components/annotation/shared/ColorPicker.tsx b/frontend/src/core/components/annotation/shared/ColorPicker.tsx index 40bb363b4..04ae501bb 100644 --- a/frontend/src/core/components/annotation/shared/ColorPicker.tsx +++ b/frontend/src/core/components/annotation/shared/ColorPicker.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Modal, Stack, ColorPicker as MantineColorPicker, Group, Button, ColorSwatch } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; interface ColorPickerProps { isOpen: boolean; @@ -14,13 +15,16 @@ export const ColorPicker: React.FC = ({ onClose, selectedColor, onColorChange, - title = "Choose Color" + title }) => { + const { t } = useTranslation(); + const resolvedTitle = title ?? t('colorPicker.title', 'Choose colour'); + return ( @@ -36,7 +40,7 @@ export const ColorPicker: React.FC = ({ /> @@ -64,4 +68,4 @@ export const ColorSwatchButton: React.FC = ({ onClick={onClick} /> ); -}; \ No newline at end of file +}; diff --git a/frontend/src/core/components/annotation/shared/DrawingCanvas.tsx b/frontend/src/core/components/annotation/shared/DrawingCanvas.tsx index 4b9de1cbb..3c75f9e6f 100644 --- a/frontend/src/core/components/annotation/shared/DrawingCanvas.tsx +++ b/frontend/src/core/components/annotation/shared/DrawingCanvas.tsx @@ -1,5 +1,6 @@ -import React, { useRef, useState } from 'react'; -import { Paper, Button, Modal, Stack, Text, Popover, ColorPicker as MantineColorPicker } from '@mantine/core'; +import React, { useEffect, useRef, useState } from 'react'; +import { Paper, Button, Modal, Stack, Text, Group } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; import { ColorSwatchButton } from '@app/components/annotation/shared/ColorPicker'; import PenSizeSelector from '@app/components/tools/sign/PenSizeSelector'; import SignaturePad from 'signature_pad'; @@ -19,6 +20,7 @@ interface DrawingCanvasProps { modalWidth?: number; modalHeight?: number; additionalButtons?: React.ReactNode; + initialSignatureData?: string; } export const DrawingCanvas: React.FC = ({ @@ -33,12 +35,13 @@ export const DrawingCanvas: React.FC = ({ disabled = false, width = 400, height = 150, + initialSignatureData, }) => { + const { t } = useTranslation(); const previewCanvasRef = useRef(null); const modalCanvasRef = useRef(null); const padRef = useRef(null); const [modalOpen, setModalOpen] = useState(false); - const [colorPickerOpen, setColorPickerOpen] = useState(false); const initPad = (canvas: HTMLCanvasElement) => { if (!padRef.current) { @@ -103,36 +106,33 @@ export const DrawingCanvas: React.FC = ({ return trimmedCanvas.toDataURL('image/png'); }; + const renderPreview = (dataUrl: string) => { + const canvas = previewCanvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + const img = new Image(); + img.onload = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + const scale = Math.min(canvas.width / img.width, canvas.height / img.height); + const scaledWidth = img.width * scale; + const scaledHeight = img.height * scale; + const x = (canvas.width - scaledWidth) / 2; + const y = (canvas.height - scaledHeight) / 2; + + ctx.drawImage(img, x, y, scaledWidth, scaledHeight); + }; + img.src = dataUrl; + }; + const closeModal = () => { if (padRef.current && !padRef.current.isEmpty()) { const canvas = modalCanvasRef.current; if (canvas) { const trimmedPng = trimCanvas(canvas); onSignatureDataChange(trimmedPng); - - // Update preview canvas with proper aspect ratio - const img = new Image(); - img.onload = () => { - if (previewCanvasRef.current) { - const ctx = previewCanvasRef.current.getContext('2d'); - if (ctx) { - ctx.clearRect(0, 0, previewCanvasRef.current.width, previewCanvasRef.current.height); - - // Calculate scaling to fit within preview canvas while maintaining aspect ratio - const scale = Math.min( - previewCanvasRef.current.width / img.width, - previewCanvasRef.current.height / img.height - ); - const scaledWidth = img.width * scale; - const scaledHeight = img.height * scale; - const x = (previewCanvasRef.current.width - scaledWidth) / 2; - const y = (previewCanvasRef.current.height - scaledHeight) / 2; - - ctx.drawImage(img, x, y, scaledWidth, scaledHeight); - } - } - }; - img.src = trimmedPng; + renderPreview(trimmedPng); if (onDrawingComplete) { onDrawingComplete(); @@ -172,11 +172,33 @@ export const DrawingCanvas: React.FC = ({ } }; + useEffect(() => { + updatePenColor(selectedColor); + }, [selectedColor]); + + useEffect(() => { + updatePenSize(penSize); + }, [penSize]); + + useEffect(() => { + const canvas = previewCanvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + if (!initialSignatureData) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + return; + } + + renderPreview(initialSignatureData); + }, [initialSignatureData]); + return ( <> - Draw your signature + {t('sign.canvas.heading', 'Draw your signature')} = ({ onClick={disabled ? undefined : openModal} /> - Click to open drawing canvas + {t('sign.canvas.clickToOpen', 'Click to open the drawing canvas')} - + -
-
- Color - - -
- setColorPickerOpen(!colorPickerOpen)} - /> -
-
- - { - onColorSwatchClick(); - updatePenColor(color); - }} - swatches={['#000000', '#0066cc', '#cc0000', '#cc6600', '#009900', '#6600cc']} - /> - -
-
-
- Pen Size + + + + {t('sign.canvas.colorLabel', 'Colour')} + + + + + + {t('sign.canvas.penSizeLabel', 'Pen size')} + = ({ updatePenSize(size); }} onInputChange={onPenSizeInputChange} - placeholder="Size" + placeholder={t('sign.canvas.penSizePlaceholder', 'Size')} size="compact-sm" - style={{ width: '60px' }} + style={{ width: '80px' }} /> -
-
+
+ { @@ -266,10 +269,10 @@ export const DrawingCanvas: React.FC = ({
diff --git a/frontend/src/core/components/annotation/shared/DrawingControls.tsx b/frontend/src/core/components/annotation/shared/DrawingControls.tsx index 62c7c615f..3c28a594e 100644 --- a/frontend/src/core/components/annotation/shared/DrawingControls.tsx +++ b/frontend/src/core/components/annotation/shared/DrawingControls.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Group, Button } from '@mantine/core'; +import { Group, Button, ActionIcon, Tooltip } from '@mantine/core'; import { useTranslation } from 'react-i18next'; +import { LocalIcon } from '@app/components/shared/LocalIcon'; interface DrawingControlsProps { onUndo?: () => void; @@ -8,8 +9,11 @@ interface DrawingControlsProps { onPlaceSignature?: () => void; hasSignatureData?: boolean; disabled?: boolean; + canUndo?: boolean; + canRedo?: boolean; showPlaceButton?: boolean; placeButtonText?: string; + additionalControls?: React.ReactNode; } export const DrawingControls: React.FC = ({ @@ -18,30 +22,48 @@ export const DrawingControls: React.FC = ({ onPlaceSignature, hasSignatureData = false, disabled = false, + canUndo = true, + canRedo = true, showPlaceButton = true, - placeButtonText = "Update and Place" + placeButtonText = "Update and Place", + additionalControls, }) => { const { t } = useTranslation(); + const undoDisabled = disabled || !canUndo; + const redoDisabled = disabled || !canRedo; return ( - - {/* Undo/Redo Controls */} - - + + {onUndo && ( + + + + + + )} + {onRedo && ( + + + + + + )} + + {additionalControls} {/* Place Signature Button */} {showPlaceButton && onPlaceSignature && ( @@ -50,11 +72,11 @@ export const DrawingControls: React.FC = ({ color="blue" onClick={onPlaceSignature} disabled={disabled || !hasSignatureData} - flex={1} + ml="auto" > {placeButtonText} )} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx b/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx index aca7430ce..c6d9d03a9 100644 --- a/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx +++ b/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx @@ -53,7 +53,7 @@ export const TextInputWithFont: React.FC = ({ return ( onTextChange(e.target.value)} @@ -63,7 +63,7 @@ export const TextInputWithFont: React.FC = ({ {/* Font Selection */}