From 4e9cdc91a63dd521f2e1afd3c7f73acf998f0e1a Mon Sep 17 00:00:00 2001 From: Reece Date: Fri, 12 Dec 2025 18:17:42 +0000 Subject: [PATCH] Remove annotation ligic from signature bridge/context --- .../annotation/shared/TextInputWithFont.tsx | 23 ++- .../components/annotation/tools/TextTool.tsx | 79 +++++--- .../components/viewer/AnnotationAPIBridge.tsx | 6 +- .../core/components/viewer/EmbedPdfViewer.tsx | 4 +- .../components/viewer/SignatureAPIBridge.tsx | 189 ++---------------- .../src/core/components/viewer/viewerTypes.ts | 6 +- .../src/core/contexts/SignatureContext.tsx | 5 +- .../hooks/tools/sign/useSignParameters.ts | 2 + frontend/src/core/tools/Annotate.tsx | 113 ++++++----- 9 files changed, 153 insertions(+), 274 deletions(-) diff --git a/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx b/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx index c93a89be0..4af2a6236 100644 --- a/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx +++ b/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Stack, TextInput, Select, Combobox, useCombobox, Group, Box } from '@mantine/core'; +import { Stack, TextInput, Select, Combobox, useCombobox, Group, Box, SegmentedControl } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { ColorPicker } from '@app/components/annotation/shared/ColorPicker'; @@ -12,6 +12,8 @@ interface TextInputWithFontProps { onFontFamilyChange: (family: string) => void; textColor?: string; onTextColorChange?: (color: string) => void; + textAlign?: 'left' | 'center' | 'right'; + onTextAlignChange?: (align: 'left' | 'center' | 'right') => void; disabled?: boolean; label?: string; placeholder?: string; @@ -27,6 +29,8 @@ export const TextInputWithFont: React.FC = ({ onFontFamilyChange, textColor = '#000000', onTextColorChange, + textAlign = 'left', + onTextAlignChange, disabled = false, label, placeholder, @@ -206,6 +210,23 @@ export const TextInputWithFont: React.FC = ({ }} /> )} + + {/* Text Alignment */} + {onTextAlignChange && ( + { + onTextAlignChange(value as 'left' | 'center' | 'right'); + onAnyChange?.(); + }} + disabled={disabled} + data={[ + { label: t('textAlign.left', 'Left'), value: 'left' }, + { label: t('textAlign.center', 'Center'), value: 'center' }, + { label: t('textAlign.right', 'Right'), value: 'right' }, + ]} + /> + )} ); }; diff --git a/frontend/src/core/components/annotation/tools/TextTool.tsx b/frontend/src/core/components/annotation/tools/TextTool.tsx index 471198d8e..ae1f1f8ef 100644 --- a/frontend/src/core/components/annotation/tools/TextTool.tsx +++ b/frontend/src/core/components/annotation/tools/TextTool.tsx @@ -1,57 +1,74 @@ -import React, { useState } from 'react'; -import { Stack } from '@mantine/core'; -import { BaseAnnotationTool } from '@app/components/annotation/shared/BaseAnnotationTool'; +import React, { useState, useEffect } from 'react'; +import { Stack, Button } from '@mantine/core'; import { TextInputWithFont } from '@app/components/annotation/shared/TextInputWithFont'; +import { useSignature } from '@app/contexts/SignatureContext'; interface TextToolProps { onTextChange?: (text: string) => void; disabled?: boolean; } +const textAlignToNumber = (align: 'left' | 'center' | 'right'): number => { + switch (align) { + case 'left': return 0; + case 'center': return 1; + case 'right': return 2; + } +}; + export const TextTool: React.FC = ({ onTextChange, disabled = false }) => { + const { signatureApiRef } = useSignature(); const [text, setText] = useState(''); const [fontSize, setFontSize] = useState(16); const [fontFamily, setFontFamily] = useState('Helvetica'); + const [textColor, setTextColor] = useState('#000000'); + const [textAlign, setTextAlign] = useState<'left' | 'center' | 'right'>('left'); const handleTextChange = (newText: string) => { setText(newText); onTextChange?.(newText); }; - const handleSignatureDataChange = (data: string | null) => { - if (data) { - onTextChange?.(data); + const handlePlaceText = () => { + const api = signatureApiRef?.current; + if (api && text) { + api.activateAnnotationTool?.('text', { + color: textColor, + fontSize, + fontFamily, + textAlign: textAlignToNumber(textAlign), + contents: text, + }); } }; - const toolConfig = { - enableTextInput: true, - showPlaceButton: true, - placeButtonText: "Place Text" - }; - return ( - - - - - + + + + ); }; \ No newline at end of file diff --git a/frontend/src/core/components/viewer/AnnotationAPIBridge.tsx b/frontend/src/core/components/viewer/AnnotationAPIBridge.tsx index d1f880fc5..7e37b3adb 100644 --- a/frontend/src/core/components/viewer/AnnotationAPIBridge.tsx +++ b/frontend/src/core/components/viewer/AnnotationAPIBridge.tsx @@ -66,17 +66,19 @@ export const AnnotationAPIBridge = forwardRef(function Annotation case 'text': return { type: PdfAnnotationSubtype.FREETEXT, - textColor: options?.color ?? '#111111', + fontColor: options?.color ?? '#111111', fontSize: options?.fontSize ?? 14, fontFamily: options?.fontFamily ?? 'Helvetica', + textAlign: options?.textAlign ?? 0, // 0 = Left, 1 = Center, 2 = Right opacity: options?.opacity ?? 1, - interiorColor: options?.fillColor ?? '#fffef7', + backgroundColor: options?.fillColor ?? '#fffef7', borderWidth: options?.thickness ?? 1, }; case 'note': return { type: PdfAnnotationSubtype.TEXT, color: options?.color ?? '#ffa000', + backgroundColor: '#ffff00', opacity: options?.opacity ?? 1, icon: getIconEnum(options?.icon), contents: options?.contents ?? '', diff --git a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx index 6a605aab4..2ad3c0ff9 100644 --- a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx +++ b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx @@ -11,7 +11,6 @@ import { ThumbnailSidebar } from '@app/components/viewer/ThumbnailSidebar'; import { BookmarkSidebar } from '@app/components/viewer/BookmarkSidebar'; import { useNavigationGuard, useNavigationState } from '@app/contexts/NavigationContext'; import { useSignature } from '@app/contexts/SignatureContext'; -import { useAnnotation } from '@app/contexts/AnnotationContext'; import { createStirlingFilesAndStubs } from '@app/services/fileStubHelpers'; import NavigationWarningModal from '@app/components/shared/NavigationWarningModal'; import { isStirlingFile } from '@app/types/fileContext'; @@ -69,8 +68,7 @@ const EmbedPdfViewerContent = ({ }, [rotationState.rotation]); // Get signature and annotation contexts - const { signatureApiRef, historyApiRef, signatureConfig, isPlacementMode } = useSignature(); - const { annotationApiRef } = useAnnotation(); + const { signatureApiRef, annotationApiRef, historyApiRef, signatureConfig, isPlacementMode } = useSignature(); // Get current file from FileContext const { selectors, state } = useFileState(); diff --git a/frontend/src/core/components/viewer/SignatureAPIBridge.tsx b/frontend/src/core/components/viewer/SignatureAPIBridge.tsx index 3d6102e7b..c5baa8254 100644 --- a/frontend/src/core/components/viewer/SignatureAPIBridge.tsx +++ b/frontend/src/core/components/viewer/SignatureAPIBridge.tsx @@ -1,8 +1,8 @@ import { useImperativeHandle, forwardRef, useEffect, useCallback, useRef, useState } from 'react'; import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react'; -import { PdfAnnotationSubtype, PdfAnnotationIcon, uuidV4 } from '@embedpdf/models'; +import { PdfAnnotationSubtype, uuidV4 } from '@embedpdf/models'; import { useSignature } from '@app/contexts/SignatureContext'; -import type { AnnotationToolId, AnnotationToolOptions, SignatureAPI } from '@app/components/viewer/viewerTypes'; +import type { SignatureAPI } from '@app/components/viewer/viewerTypes'; import type { SignParameters } from '@app/hooks/tools/sign/useSignParameters'; import { useViewer } from '@app/contexts/ViewerContext'; @@ -104,12 +104,20 @@ const createTextStampImage = ( ctx.fillStyle = textColor; ctx.font = `${fontSize}px ${fontFamily}`; - ctx.textAlign = 'left'; + ctx.textAlign = config.textAlign || 'left'; ctx.textBaseline = 'middle'; const horizontalPadding = paddingX; const verticalCenter = naturalHeight / 2; - ctx.fillText(text, horizontalPadding, verticalCenter); + + let xPosition = horizontalPadding; + if (config.textAlign === 'center') { + xPosition = naturalWidth / 2; + } else if (config.textAlign === 'right') { + xPosition = naturalWidth - horizontalPadding; + } + + ctx.fillText(text, xPosition, verticalCenter); return { dataUrl: canvas.toDataURL('image/png'), @@ -199,157 +207,6 @@ export const SignatureAPIBridge = forwardRef(function SignatureAPI } }, [annotationApi, signatureConfig, placementPreviewSize, applyStampDefaults, cssToPdfSize]); - const getIconEnum = (icon?: string): PdfAnnotationIcon => { - switch (icon) { - case 'Comment': return PdfAnnotationIcon.Comment; - case 'Key': return PdfAnnotationIcon.Key; - case 'Note': return PdfAnnotationIcon.Note; - case 'Help': return PdfAnnotationIcon.Help; - case 'NewParagraph': return PdfAnnotationIcon.NewParagraph; - case 'Paragraph': return PdfAnnotationIcon.Paragraph; - case 'Insert': return PdfAnnotationIcon.Insert; - default: return PdfAnnotationIcon.Comment; - } - }; - - const buildAnnotationDefaults = useCallback( - (toolId: AnnotationToolId, options?: AnnotationToolOptions) => { - switch (toolId) { - case 'highlight': - return { - type: PdfAnnotationSubtype.HIGHLIGHT, - color: options?.color ?? '#ffd54f', - opacity: options?.opacity ?? 0.6, - }; - case 'underline': - return { - type: PdfAnnotationSubtype.UNDERLINE, - color: options?.color ?? '#ffb300', - opacity: options?.opacity ?? 1, - }; - case 'strikeout': - return { - type: PdfAnnotationSubtype.STRIKEOUT, - color: options?.color ?? '#e53935', - opacity: options?.opacity ?? 1, - }; - case 'squiggly': - return { - type: PdfAnnotationSubtype.SQUIGGLY, - color: options?.color ?? '#00acc1', - opacity: options?.opacity ?? 1, - }; - case 'ink': - return { - type: PdfAnnotationSubtype.INK, - color: options?.color ?? '#1f2933', - opacity: options?.opacity ?? 1, - borderWidth: options?.thickness ?? 2, - lineWidth: options?.thickness ?? 2, - strokeWidth: options?.thickness ?? 2, - }; - case 'inkHighlighter': - return { - type: PdfAnnotationSubtype.INK, - color: options?.color ?? '#ffd54f', - opacity: options?.opacity ?? 0.5, - borderWidth: options?.thickness ?? 6, - lineWidth: options?.thickness ?? 6, - strokeWidth: options?.thickness ?? 6, - }; - case 'text': - return { - type: PdfAnnotationSubtype.FREETEXT, - textColor: options?.color ?? '#111111', - fontSize: options?.fontSize ?? 14, - fontFamily: options?.fontFamily ?? 'Helvetica', - opacity: options?.opacity ?? 1, - interiorColor: options?.fillColor ?? '#fffef7', - borderWidth: options?.thickness ?? 1, - }; - case 'note': - return { - type: PdfAnnotationSubtype.TEXT, - color: options?.color ?? '#ffa000', - opacity: options?.opacity ?? 1, - icon: getIconEnum(options?.icon), - contents: options?.contents ?? '', - }; - case 'square': - return { - type: PdfAnnotationSubtype.SQUARE, - color: options?.color ?? '#1565c0', - interiorColor: options?.fillColor ?? '#e3f2fd', - opacity: options?.opacity ?? 0.35, - borderWidth: options?.thickness ?? 2, - }; - case 'circle': - return { - type: PdfAnnotationSubtype.CIRCLE, - color: options?.color ?? '#1565c0', - interiorColor: options?.fillColor ?? '#e3f2fd', - opacity: options?.opacity ?? 0.35, - borderWidth: options?.thickness ?? 2, - }; - case 'line': - return { - type: PdfAnnotationSubtype.LINE, - color: options?.color ?? '#1565c0', - opacity: options?.opacity ?? 1, - borderWidth: options?.thickness ?? 2, - }; - case 'lineArrow': - return { - type: PdfAnnotationSubtype.LINE, - color: options?.color ?? '#1565c0', - opacity: options?.opacity ?? 1, - borderWidth: options?.thickness ?? 2, - startStyle: 'None', - endStyle: 'ClosedArrow', - lineEndingStyles: { start: 'None', end: 'ClosedArrow' }, - }; - case 'polyline': - return { - type: PdfAnnotationSubtype.POLYLINE, - color: options?.color ?? '#1565c0', - opacity: options?.opacity ?? 1, - borderWidth: options?.thickness ?? 2, - }; - case 'polygon': - return { - type: PdfAnnotationSubtype.POLYGON, - color: options?.color ?? '#1565c0', - interiorColor: options?.fillColor ?? '#e3f2fd', - opacity: options?.opacity ?? 0.35, - borderWidth: options?.thickness ?? 2, - }; - case 'stamp': - return { - type: PdfAnnotationSubtype.STAMP, - }; - case 'select': - default: - return null; - } - }, - [] - ); - - const configureAnnotationTool = useCallback( - (toolId: AnnotationToolId, options?: AnnotationToolOptions) => { - if (!annotationApi) return; - - const defaults = buildAnnotationDefaults(toolId, options); - const api = annotationApi as any; - - if (defaults) { - api.setToolDefaults?.(toolId, defaults); - } - - api.setActiveTool?.(toolId === 'select' ? null : toolId); - }, - [annotationApi, buildAnnotationDefaults] - ); // Enable keyboard deletion of selected annotations useEffect(() => { @@ -531,27 +388,7 @@ export const SignatureAPIBridge = forwardRef(function SignatureAPI return []; } }, - activateAnnotationTool: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => { - configureAnnotationTool(toolId, options); - }, - setAnnotationStyle: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => { - const defaults = buildAnnotationDefaults(toolId, options); - const api = annotationApi as any; - if (defaults && api?.setToolDefaults) { - api.setToolDefaults(toolId, defaults); - } - }, - getSelectedAnnotation: () => { - return annotationApi?.getSelectedAnnotation?.() ?? null; - }, - deselectAnnotation: () => { - const api = annotationApi as any; - api?.deselectAnnotation?.(); - }, - updateAnnotation: (pageIndex: number, annotationId: string, patch: Partial) => { - annotationApi?.updateAnnotation?.(pageIndex, annotationId, patch); - }, - }), [annotationApi, signatureConfig, placementPreviewSize, applyStampDefaults, configureAnnotationTool, buildAnnotationDefaults]); + }), [annotationApi, signatureConfig, placementPreviewSize, applyStampDefaults]); useEffect(() => { if (!annotationApi?.onAnnotationEvent) { diff --git a/frontend/src/core/components/viewer/viewerTypes.ts b/frontend/src/core/components/viewer/viewerTypes.ts index e150da9d2..53097a066 100644 --- a/frontend/src/core/components/viewer/viewerTypes.ts +++ b/frontend/src/core/components/viewer/viewerTypes.ts @@ -14,11 +14,6 @@ export interface SignatureAPI { updateDrawSettings: (color: string, size: number) => void; deactivateTools: () => void; getPageAnnotations: (pageIndex: number) => Promise; - activateAnnotationTool?: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => void; - setAnnotationStyle?: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => void; - getSelectedAnnotation?: () => any | null; - deselectAnnotation?: () => void; - updateAnnotation?: (pageIndex: number, annotationId: string, patch: Partial) => void; } export interface AnnotationAPI { @@ -67,6 +62,7 @@ export interface AnnotationToolOptions { thickness?: number; fontSize?: number; fontFamily?: string; + textAlign?: number; // 0 = Left, 1 = Center, 2 = Right imageSrc?: string; icon?: 'Comment' | 'Key' | 'Note' | 'Help' | 'NewParagraph' | 'Paragraph' | 'Insert'; contents?: string; diff --git a/frontend/src/core/contexts/SignatureContext.tsx b/frontend/src/core/contexts/SignatureContext.tsx index 24fdc22cb..e8f10fc40 100644 --- a/frontend/src/core/contexts/SignatureContext.tsx +++ b/frontend/src/core/contexts/SignatureContext.tsx @@ -1,6 +1,6 @@ import React, { createContext, useContext, useState, ReactNode, useCallback, useRef } from 'react'; import { SignParameters } from '@app/hooks/tools/sign/useSignParameters'; -import type { SignatureAPI, HistoryAPI } from '@app/components/viewer/viewerTypes'; +import type { SignatureAPI, HistoryAPI, AnnotationAPI } from '@app/components/viewer/viewerTypes'; // Signature state interface interface SignatureState { @@ -34,6 +34,7 @@ interface SignatureActions { // Combined context interface interface SignatureContextValue extends SignatureState, SignatureActions { signatureApiRef: React.RefObject; + annotationApiRef: React.RefObject; historyApiRef: React.RefObject; } @@ -52,6 +53,7 @@ const initialState: SignatureState = { export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const [state, setState] = useState(initialState); const signatureApiRef = useRef(null); + const annotationApiRef = useRef(null); const historyApiRef = useRef(null); const imageDataStore = useRef>(new Map()); @@ -157,6 +159,7 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children const contextValue: SignatureContextValue = { ...state, signatureApiRef, + annotationApiRef, historyApiRef, setSignatureConfig, setPlacementMode, diff --git a/frontend/src/core/hooks/tools/sign/useSignParameters.ts b/frontend/src/core/hooks/tools/sign/useSignParameters.ts index c410afe3c..126f5d580 100644 --- a/frontend/src/core/hooks/tools/sign/useSignParameters.ts +++ b/frontend/src/core/hooks/tools/sign/useSignParameters.ts @@ -18,6 +18,7 @@ export interface SignParameters { fontFamily?: string; fontSize?: number; textColor?: string; + textAlign?: 'left' | 'center' | 'right'; } export const DEFAULT_PARAMETERS: SignParameters = { @@ -28,6 +29,7 @@ export const DEFAULT_PARAMETERS: SignParameters = { fontFamily: 'Helvetica', fontSize: 16, textColor: '#000000', + textAlign: 'left', }; const validateSignParameters = (parameters: SignParameters): boolean => { diff --git a/frontend/src/core/tools/Annotate.tsx b/frontend/src/core/tools/Annotate.tsx index 4137c4fef..c2ea03f37 100644 --- a/frontend/src/core/tools/Annotate.tsx +++ b/frontend/src/core/tools/Annotate.tsx @@ -20,6 +20,7 @@ const Annotate = (_props: BaseToolProps) => { const { selectors } = useFileContext(); const { signatureApiRef, + annotationApiRef, historyApiRef, undo, redo, @@ -109,7 +110,8 @@ const Annotate = (_props: BaseToolProps) => { case 'squiggly': return { color: squigglyColor, opacity: squigglyOpacity / 100, ...metadata }; case 'text': - return { color: textColor, fontSize: textSize, textAlign: textAlignment, ...metadata }; + const textAlignNumber = textAlignment === 'left' ? 0 : textAlignment === 'center' ? 1 : 2; + return { color: textColor, fontSize: textSize, textAlign: textAlignNumber, ...metadata }; case 'square': case 'circle': case 'polygon': @@ -163,7 +165,7 @@ const Annotate = (_props: BaseToolProps) => { if (viewerContext.isAnnotationMode) return; viewerContext.setAnnotationMode(true); - signatureApiRef?.current?.activateAnnotationTool?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.activateAnnotationTool?.(activeTool, buildToolOptions(activeTool)); }, [viewerContext?.isAnnotationMode, signatureApiRef, activeTool, buildToolOptions]); const activateAnnotationTool = (toolId: AnnotationToolId) => { @@ -179,7 +181,7 @@ const Annotate = (_props: BaseToolProps) => { manualToolSwitch.current = true; // Deselect annotation in the viewer first - signatureApiRef?.current?.deselectAnnotation?.(); + annotationApiRef?.current?.deselectAnnotation?.(); // Clear selection state to show default controls setSelectedAnn(null); @@ -191,10 +193,10 @@ const Annotate = (_props: BaseToolProps) => { // For stamp, apply the image if we have one if (toolId === 'stamp' && stampImageData) { - signatureApiRef?.current?.setAnnotationStyle?.('stamp', { imageSrc: stampImageData }); - signatureApiRef?.current?.activateAnnotationTool?.('stamp', { imageSrc: stampImageData }); + annotationApiRef?.current?.setAnnotationStyle?.('stamp', { imageSrc: stampImageData }); + annotationApiRef?.current?.activateAnnotationTool?.('stamp', { imageSrc: stampImageData }); } else { - signatureApiRef?.current?.activateAnnotationTool?.(toolId, options); + annotationApiRef?.current?.activateAnnotationTool?.(toolId, options); } // Reset flag after a short delay @@ -206,9 +208,9 @@ const Annotate = (_props: BaseToolProps) => { useEffect(() => { // push style updates to EmbedPDF when sliders/colors change if (activeTool === 'stamp' && stampImageData) { - signatureApiRef?.current?.setAnnotationStyle?.('stamp', { imageSrc: stampImageData }); + annotationApiRef?.current?.setAnnotationStyle?.('stamp', { imageSrc: stampImageData }); } else { - signatureApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); } }, [activeTool, buildToolOptions, signatureApiRef, stampImageData]); @@ -219,7 +221,7 @@ const Annotate = (_props: BaseToolProps) => { // and apply the converted size to the stamp tool automatically if (activeTool === 'stamp' && placementPreviewSize && stampImageData) { // Just update the image source; size is handled by SignatureAPIBridge - signatureApiRef?.current?.setAnnotationStyle?.('stamp', { + annotationApiRef?.current?.setAnnotationStyle?.('stamp', { imageSrc: stampImageData, }); } @@ -230,10 +232,10 @@ const Annotate = (_props: BaseToolProps) => { const handler = (e: KeyboardEvent) => { if (e.key !== 'Escape') return; if (['polyline', 'polygon'].includes(activeTool)) { - signatureApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); - signatureApiRef?.current?.activateAnnotationTool?.(null as any); + annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.activateAnnotationTool?.(null as any); setTimeout(() => { - signatureApiRef?.current?.activateAnnotationTool?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.activateAnnotationTool?.(activeTool, buildToolOptions(activeTool)); }, 50); } }; @@ -244,7 +246,7 @@ const Annotate = (_props: BaseToolProps) => { // Poll selected annotation to allow editing existing highlights/text useEffect(() => { const interval = setInterval(() => { - const ann = signatureApiRef?.current?.getSelectedAnnotation?.(); + const ann = annotationApiRef?.current?.getSelectedAnnotation?.(); const annId = ann?.object?.id ?? null; // Only update state when selection actually changes if (annId !== selectedAnnId) { @@ -321,6 +323,7 @@ const Annotate = (_props: BaseToolProps) => { const otherTools: { id: AnnotationToolId; label: string; icon: string }[] = [ { id: 'text', label: t('annotation.text', 'Text box'), icon: 'text-fields' }, + { id: 'note', label: t('annotation.note', 'Note'), icon: 'sticky-note-2' }, { id: 'stamp', label: t('annotation.stamp', 'Add Image'), icon: 'add-photo-alternate' }, ]; @@ -391,8 +394,8 @@ const Annotate = (_props: BaseToolProps) => { setTimeout(() => { viewerContext?.setAnnotationMode(true); setPlacementMode(true); // This shows the preview overlay - signatureApiRef?.current?.setAnnotationStyle?.('stamp', { imageSrc: dataUrl }); - signatureApiRef?.current?.activateAnnotationTool?.('stamp', { imageSrc: dataUrl }); + annotationApiRef?.current?.setAnnotationStyle?.('stamp', { imageSrc: dataUrl }); + annotationApiRef?.current?.activateAnnotationTool?.('stamp', { imageSrc: dataUrl }); }, 150); } catch (err) { console.error('Failed to load stamp image', err); @@ -603,7 +606,7 @@ const Annotate = (_props: BaseToolProps) => { max={100} value={Math.round(((selectedAnn.object?.opacity ?? 1) * 100) || 100)} onChange={(value) => { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { opacity: value / 100 } @@ -638,7 +641,7 @@ const Annotate = (_props: BaseToolProps) => { max={12} value={selectedAnn.object?.borderWidth ?? inkWidth} onChange={(value) => { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { @@ -698,7 +701,7 @@ const Annotate = (_props: BaseToolProps) => { clearTimeout(selectedUpdateTimer.current); } selectedUpdateTimer.current = setTimeout(() => { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { contents: newVal, textColor: selectedAnn.object?.textColor ?? textColor } @@ -713,7 +716,7 @@ const Annotate = (_props: BaseToolProps) => { clearTimeout(selectedUpdateTimer.current); } selectedUpdateTimer.current = setTimeout(() => { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { contents: val, textColor: selectedAnn.object?.textColor ?? textColor } @@ -729,7 +732,7 @@ const Annotate = (_props: BaseToolProps) => { value={selectedFontSize} onChange={(size) => { setSelectedFontSize(size); - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { fontSize: size } @@ -743,7 +746,7 @@ const Annotate = (_props: BaseToolProps) => { { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { textAlign: 'left' } @@ -756,7 +759,7 @@ const Annotate = (_props: BaseToolProps) => { { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { textAlign: 'center' } @@ -769,7 +772,7 @@ const Annotate = (_props: BaseToolProps) => { { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { textAlign: 'right' } @@ -808,7 +811,7 @@ const Annotate = (_props: BaseToolProps) => { max={100} value={Math.round(((selectedAnn.object?.opacity ?? 1) * 100) || 100)} onChange={(value) => { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { opacity: value / 100 } @@ -823,7 +826,7 @@ const Annotate = (_props: BaseToolProps) => { max={12} value={selectedAnn.object?.borderWidth ?? shapeThickness} onChange={(value) => { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { @@ -877,7 +880,7 @@ const Annotate = (_props: BaseToolProps) => { max={100} value={Math.round(((selectedAnn.object?.opacity ?? 1) * 100) || 100)} onChange={(value) => { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { opacity: value / 100 } @@ -893,7 +896,7 @@ const Annotate = (_props: BaseToolProps) => { max={12} value={selectedAnn.object?.borderWidth ?? shapeThickness} onChange={(value) => { - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { @@ -911,7 +914,7 @@ const Annotate = (_props: BaseToolProps) => { variant={(selectedAnn.object?.borderWidth ?? shapeThickness) === 0 ? 'filled' : 'light'} onClick={() => { const newValue = (selectedAnn.object?.borderWidth ?? shapeThickness) === 0 ? 1 : 0; - signatureApiRef?.current?.updateAnnotation?.( + annotationApiRef?.current?.updateAnnotation?.( selectedAnn.object?.pageIndex ?? 0, selectedAnn.object?.id, { @@ -989,40 +992,40 @@ const Annotate = (_props: BaseToolProps) => { if (colorPickerTarget === 'highlight') { setHighlightOpacity(opacity); if (activeTool === 'highlight' || activeTool === 'inkHighlighter') { - signatureApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); } if (selectedAnn?.object?.id && (selectedAnn.object?.type === 9 || selectedAnn.object?.type === 15)) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 }); } } else if (colorPickerTarget === 'underline') { setUnderlineOpacity(opacity); - signatureApiRef?.current?.setAnnotationStyle?.('underline', buildToolOptions('underline')); + annotationApiRef?.current?.setAnnotationStyle?.('underline', buildToolOptions('underline')); if (selectedAnn?.object?.id && selectedAnn.object?.type === 10) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 }); } } else if (colorPickerTarget === 'strikeout') { setStrikeoutOpacity(opacity); - signatureApiRef?.current?.setAnnotationStyle?.('strikeout', buildToolOptions('strikeout')); + annotationApiRef?.current?.setAnnotationStyle?.('strikeout', buildToolOptions('strikeout')); if (selectedAnn?.object?.id && selectedAnn.object?.type === 12) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 }); } } else if (colorPickerTarget === 'squiggly') { setSquigglyOpacity(opacity); - signatureApiRef?.current?.setAnnotationStyle?.('squiggly', buildToolOptions('squiggly')); + annotationApiRef?.current?.setAnnotationStyle?.('squiggly', buildToolOptions('squiggly')); if (selectedAnn?.object?.id && selectedAnn.object?.type === 11) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 }); } } else if (colorPickerTarget === 'shapeStroke') { setShapeStrokeOpacity(opacity); const shapeTools = ['square', 'circle', 'polygon'] as AnnotationToolId[]; if (shapeTools.includes(activeTool)) { - signatureApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); } } else if (colorPickerTarget === 'shapeFill') { setShapeFillOpacity(opacity); const fillShapeTools = ['square', 'circle', 'polygon'] as AnnotationToolId[]; if (fillShapeTools.includes(activeTool)) { - signatureApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); } } }} @@ -1030,44 +1033,44 @@ const Annotate = (_props: BaseToolProps) => { if (colorPickerTarget === 'ink') { setInkColor(color); if (activeTool === 'ink') { - signatureApiRef?.current?.setAnnotationStyle?.('ink', buildToolOptions('ink')); + annotationApiRef?.current?.setAnnotationStyle?.('ink', buildToolOptions('ink')); } if (selectedAnn?.object?.type === 15) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); } } else if (colorPickerTarget === 'highlight') { setHighlightColor(color); if (activeTool === 'highlight' || activeTool === 'inkHighlighter') { - signatureApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); } if (selectedAnn?.object?.type === 9 || selectedAnn?.object?.type === 15) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); } } else if (colorPickerTarget === 'underline') { setUnderlineColor(color); - signatureApiRef?.current?.setAnnotationStyle?.('underline', buildToolOptions('underline')); + annotationApiRef?.current?.setAnnotationStyle?.('underline', buildToolOptions('underline')); if (selectedAnn?.object?.id) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); } } else if (colorPickerTarget === 'strikeout') { setStrikeoutColor(color); - signatureApiRef?.current?.setAnnotationStyle?.('strikeout', buildToolOptions('strikeout')); + annotationApiRef?.current?.setAnnotationStyle?.('strikeout', buildToolOptions('strikeout')); if (selectedAnn?.object?.id) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); } } else if (colorPickerTarget === 'squiggly') { setSquigglyColor(color); - signatureApiRef?.current?.setAnnotationStyle?.('squiggly', buildToolOptions('squiggly')); + annotationApiRef?.current?.setAnnotationStyle?.('squiggly', buildToolOptions('squiggly')); if (selectedAnn?.object?.id) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color }); } } else { setTextColor(color); if (activeTool === 'text') { - signatureApiRef?.current?.setAnnotationStyle?.('text', buildToolOptions('text')); + annotationApiRef?.current?.setAnnotationStyle?.('text', buildToolOptions('text')); } if (selectedAnn?.object?.type === 3 || selectedAnn?.object?.type === 1) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { textColor: color, color, }); @@ -1080,10 +1083,10 @@ const Annotate = (_props: BaseToolProps) => { setShapeStrokeColor(color); const styleTool = shapeTools.includes(activeTool) ? activeTool : null; if (styleTool) { - signatureApiRef?.current?.setAnnotationStyle?.(styleTool, buildToolOptions(styleTool)); + annotationApiRef?.current?.setAnnotationStyle?.(styleTool, buildToolOptions(styleTool)); } if (selectedAnn?.object?.id) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { strokeColor: color, // border color color: selectedAnn.object?.color ?? shapeFillColor, // preserve fill borderWidth: shapeThickness, @@ -1094,10 +1097,10 @@ const Annotate = (_props: BaseToolProps) => { setShapeFillColor(color); const styleTool = fillShapeTools.includes(activeTool) ? activeTool : null; if (styleTool) { - signatureApiRef?.current?.setAnnotationStyle?.(styleTool, buildToolOptions(styleTool)); + annotationApiRef?.current?.setAnnotationStyle?.(styleTool, buildToolOptions(styleTool)); } if (selectedAnn?.object?.id) { - signatureApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { + annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color, // fill color strokeColor: selectedAnn.object?.strokeColor ?? shapeStrokeColor, // preserve border borderWidth: shapeThickness, @@ -1126,7 +1129,7 @@ const Annotate = (_props: BaseToolProps) => { size="lg" onClick={() => { viewerContext?.setAnnotationMode(true); - signatureApiRef?.current?.activateAnnotationTool?.(activeTool, buildToolOptions(activeTool)); + annotationApiRef?.current?.activateAnnotationTool?.(activeTool, buildToolOptions(activeTool)); setIsAnnotationPaused(false); }} style={{