From a6227160e2f9ce3a010da04ab9f36f019626fe46 Mon Sep 17 00:00:00 2001 From: Reece Date: Tue, 9 Dec 2025 11:49:44 +0000 Subject: [PATCH] improvements --- .../public/locales/en-GB/translation.toml | 6 +- .../components/viewer/AnnotationAPIBridge.tsx | 23 +++- .../components/viewer/SignatureAPIBridge.tsx | 23 +++- .../src/core/components/viewer/viewerTypes.ts | 2 + frontend/src/core/tools/Annotate.tsx | 107 ++++++++++++------ 5 files changed, 111 insertions(+), 50 deletions(-) diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index ae129bde4..626f589a4 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -3932,7 +3932,7 @@ fillColor = "Fill Colour" underline = "Underline" strikeout = "Strikeout" squiggly = "Squiggly" -inkHighlighter = "Ink Highlighter" +inkHighlighter = "Freehand Highlighter" square = "Square" circle = "Circle" polygon = "Polygon" @@ -3955,6 +3955,10 @@ editSquare = "Edit Square" editCircle = "Edit Circle" editPolygon = "Edit Polygon" unsupportedType = "This annotation type is not fully supported for editing." +textAlignment = "Text Alignment" +noteIcon = "Note Icon" +imagePreview = "Preview" +contents = "Text" [search] title = "Search PDF" diff --git a/frontend/src/core/components/viewer/AnnotationAPIBridge.tsx b/frontend/src/core/components/viewer/AnnotationAPIBridge.tsx index 264422c01..d1f880fc5 100644 --- a/frontend/src/core/components/viewer/AnnotationAPIBridge.tsx +++ b/frontend/src/core/components/viewer/AnnotationAPIBridge.tsx @@ -1,11 +1,24 @@ import { useImperativeHandle, forwardRef, useCallback } from 'react'; import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react'; -import { PdfAnnotationSubtype } from '@embedpdf/models'; +import { PdfAnnotationSubtype, PdfAnnotationIcon } from '@embedpdf/models'; import type { AnnotationToolId, AnnotationToolOptions, AnnotationAPI } from '@app/components/viewer/viewerTypes'; export const AnnotationAPIBridge = forwardRef(function AnnotationAPIBridge(_props, ref) { const annotationApi = useAnnotationCapability(); + 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) { @@ -62,13 +75,11 @@ export const AnnotationAPIBridge = forwardRef(function Annotation }; case 'note': return { - type: PdfAnnotationSubtype.FREETEXT, - textColor: options?.color ?? '#1b1b1b', + type: PdfAnnotationSubtype.TEXT, color: options?.color ?? '#ffa000', - interiorColor: options?.fillColor ?? '#fff8e1', opacity: options?.opacity ?? 1, - fontSize: options?.fontSize ?? 12, - contents: 'Note', + icon: getIconEnum(options?.icon), + contents: options?.contents ?? '', }; case 'square': return { diff --git a/frontend/src/core/components/viewer/SignatureAPIBridge.tsx b/frontend/src/core/components/viewer/SignatureAPIBridge.tsx index 35906a5b4..3d6102e7b 100644 --- a/frontend/src/core/components/viewer/SignatureAPIBridge.tsx +++ b/frontend/src/core/components/viewer/SignatureAPIBridge.tsx @@ -1,6 +1,6 @@ import { useImperativeHandle, forwardRef, useEffect, useCallback, useRef, useState } from 'react'; import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react'; -import { PdfAnnotationSubtype, uuidV4 } from '@embedpdf/models'; +import { PdfAnnotationSubtype, PdfAnnotationIcon, uuidV4 } from '@embedpdf/models'; import { useSignature } from '@app/contexts/SignatureContext'; import type { AnnotationToolId, AnnotationToolOptions, SignatureAPI } from '@app/components/viewer/viewerTypes'; import type { SignParameters } from '@app/hooks/tools/sign/useSignParameters'; @@ -199,6 +199,19 @@ 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) { @@ -256,13 +269,11 @@ export const SignatureAPIBridge = forwardRef(function SignatureAPI }; case 'note': return { - type: PdfAnnotationSubtype.FREETEXT, - textColor: options?.color ?? '#1b1b1b', + type: PdfAnnotationSubtype.TEXT, color: options?.color ?? '#ffa000', - interiorColor: options?.fillColor ?? '#fff8e1', opacity: options?.opacity ?? 1, - fontSize: options?.fontSize ?? 12, - contents: 'Note', + icon: getIconEnum(options?.icon), + contents: options?.contents ?? '', }; case 'square': return { diff --git a/frontend/src/core/components/viewer/viewerTypes.ts b/frontend/src/core/components/viewer/viewerTypes.ts index 214c48382..e150da9d2 100644 --- a/frontend/src/core/components/viewer/viewerTypes.ts +++ b/frontend/src/core/components/viewer/viewerTypes.ts @@ -68,4 +68,6 @@ export interface AnnotationToolOptions { fontSize?: number; fontFamily?: string; imageSrc?: string; + icon?: 'Comment' | 'Key' | 'Note' | 'Help' | 'NewParagraph' | 'Paragraph' | 'Insert'; + contents?: string; } diff --git a/frontend/src/core/tools/Annotate.tsx b/frontend/src/core/tools/Annotate.tsx index 9248d26c9..d63723dc4 100644 --- a/frontend/src/core/tools/Annotate.tsx +++ b/frontend/src/core/tools/Annotate.tsx @@ -35,6 +35,7 @@ const Annotate = (_props: BaseToolProps) => { const [squigglyOpacity, setSquigglyOpacity] = useState(100); const [textColor, setTextColor] = useState('#111111'); const [textSize, setTextSize] = useState(14); + const [textAlignment, setTextAlignment] = useState<'left' | 'center' | 'right'>('left'); const [shapeStrokeColor, setShapeStrokeColor] = useState('#cf5b5b'); const [shapeFillColor, setShapeFillColor] = useState('#0000ff'); const [shapeOpacity, setShapeOpacity] = useState(50); @@ -53,24 +54,30 @@ const Annotate = (_props: BaseToolProps) => { const [historyAvailability, setHistoryAvailability] = useState({ canUndo: false, canRedo: false }); const manualToolSwitch = useRef(false); - const buildToolOptions = useCallback((toolId: AnnotationToolId) => { + const buildToolOptions = useCallback((toolId: AnnotationToolId, includeMetadata: boolean = true) => { + const metadata = includeMetadata ? { + customData: { + author: 'User', // Could be replaced with actual user name from auth context + createdAt: new Date().toISOString(), + modifiedAt: new Date().toISOString(), + } + } : {}; + switch (toolId) { case 'ink': - return { color: inkColor, thickness: inkWidth }; + return { color: inkColor, thickness: inkWidth, ...metadata }; case 'inkHighlighter': - return { color: highlightColor, opacity: highlightOpacity / 100, thickness: freehandHighlighterWidth }; + return { color: highlightColor, opacity: highlightOpacity / 100, thickness: freehandHighlighterWidth, ...metadata }; case 'highlight': - return { color: highlightColor, opacity: highlightOpacity / 100 }; + return { color: highlightColor, opacity: highlightOpacity / 100, ...metadata }; case 'underline': - return { color: underlineColor, opacity: underlineOpacity / 100 }; + return { color: underlineColor, opacity: underlineOpacity / 100, ...metadata }; case 'strikeout': - return { color: strikeoutColor, opacity: strikeoutOpacity / 100 }; + return { color: strikeoutColor, opacity: strikeoutOpacity / 100, ...metadata }; case 'squiggly': - return { color: squigglyColor, opacity: squigglyOpacity / 100 }; + return { color: squigglyColor, opacity: squigglyOpacity / 100, ...metadata }; case 'text': - return { color: textColor, fontSize: textSize }; - case 'note': - return { color: textColor }; + return { color: textColor, fontSize: textSize, textAlign: textAlignment, ...metadata }; case 'square': case 'circle': case 'polygon': @@ -81,6 +88,7 @@ const Annotate = (_props: BaseToolProps) => { strokeOpacity: shapeStrokeOpacity / 100, fillOpacity: shapeFillOpacity / 100, borderWidth: shapeThickness, + ...metadata, }; case 'line': case 'polyline': @@ -90,11 +98,12 @@ const Annotate = (_props: BaseToolProps) => { strokeColor: shapeStrokeColor, opacity: shapeStrokeOpacity / 100, borderWidth: shapeThickness, + ...metadata, }; default: return {}; } - }, [highlightColor, highlightOpacity, inkColor, inkWidth, freehandHighlighterWidth, underlineColor, underlineOpacity, strikeoutColor, strikeoutOpacity, squigglyColor, squigglyOpacity, textColor, textSize, shapeStrokeColor, shapeFillColor, shapeStrokeOpacity, shapeFillOpacity, shapeThickness]); + }, [highlightColor, highlightOpacity, inkColor, inkWidth, freehandHighlighterWidth, underlineColor, underlineOpacity, strikeoutColor, strikeoutOpacity, squigglyColor, squigglyOpacity, textColor, textSize, textAlignment, shapeStrokeColor, shapeFillColor, shapeOpacity, shapeStrokeOpacity, shapeFillOpacity, shapeThickness]); useEffect(() => { setToolAndWorkbench('annotate', 'viewer'); @@ -216,7 +225,6 @@ const Annotate = (_props: BaseToolProps) => { } } else { const typeToToolMap: Record = { - 1: 'note', // TEXT 3: 'text', // FREETEXT 4: 'line', // LINE 5: 'square', // SQUARE @@ -262,7 +270,6 @@ 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' }, ]; @@ -433,10 +440,38 @@ const Annotate = (_props: BaseToolProps) => { )} {activeTool === 'text' && ( - - {t('annotation.fontSize', 'Font size')} - - + <> + + {t('annotation.fontSize', 'Font size')} + + + + {t('annotation.textAlignment', 'Text Alignment')} + + setTextAlignment('left')} + size="md" + > + + + setTextAlignment('center')} + size="md" + > + + + setTextAlignment('right')} + size="md" + > + + + + + )} {['square', 'circle', 'line', 'polygon'].includes(activeTool) && ( @@ -559,11 +594,11 @@ const Annotate = (_props: BaseToolProps) => { ); } - // Type 3: Text box, Type 1: Note - if ([1, 3].includes(type)) { + // Type 3: Text box + if (type === 3) { return ( - {type === 3 ? t('annotation.editText', 'Edit Text Box') : t('annotation.editNote', 'Edit Note')} + {t('annotation.editText', 'Edit Text Box')} {t('annotation.color', 'Color')} { }, 120); }} /> - {type === 3 && ( - { - const size = typeof val === 'number' ? val : 14; - setSelectedFontSize(size); - signatureApiRef?.current?.updateAnnotation?.( - selectedAnn.object?.pageIndex ?? 0, - selectedAnn.object?.id, - { fontSize: size } - ); - }} - /> - )} + { + const size = typeof val === 'number' ? val : 14; + setSelectedFontSize(size); + signatureApiRef?.current?.updateAnnotation?.( + selectedAnn.object?.pageIndex ?? 0, + selectedAnn.object?.id, + { fontSize: size } + ); + }} + /> {t('annotation.opacity', 'Opacity')}