diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index f5142ef1c..6f9d24e27 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -4018,23 +4018,26 @@ deleteSelected = "Delete Selected Pages" closePdf = "Close PDF" exportAll = "Export PDF" downloadSelected = "Download Selected Files" -downloadAll = "Download All" -saveAll = "Save All" +annotations = "Annotations" +exportSelected = "Export Selected Pages" +saveChanges = "Save Changes" toggleTheme = "Toggle Theme" -toggleBookmarks = "Toggle Bookmarks" language = "Language" +toggleAnnotations = "Toggle Annotations Visibility" search = "Search PDF" panMode = "Pan Mode" rotateLeft = "Rotate Left" rotateRight = "Rotate Right" toggleSidebar = "Toggle Sidebar" -exportSelected = "Export Selected Pages" -toggleAnnotations = "Toggle Annotations Visibility" -annotationMode = "Toggle Annotation Mode" +toggleBookmarks = "Toggle Bookmarks" print = "Print PDF" -draw = "Draw" -save = "Save" -saveChanges = "Save Changes" +downloadAll = "Download All" +saveAll = "Save All" + +[textAlign] +left = "Left" +center = "Center" +right = "Right" [annotation] title = "Annotate" @@ -4060,6 +4063,7 @@ underline = "Underline" strikeout = "Strikeout" squiggly = "Squiggly" inkHighlighter = "Freehand Highlighter" +freehandHighlighter = "Freehand Highlighter" square = "Square" circle = "Circle" polygon = "Polygon" @@ -4086,6 +4090,19 @@ textAlignment = "Text Alignment" noteIcon = "Note Icon" imagePreview = "Preview" contents = "Text" +backgroundColor = "Background colour" +clearBackground = "Remove background" +noBackground = "No background" +stampSettings = "Stamp Settings" +savingCopy = "Preparing download..." +saveFailed = "Unable to save copy" +saveReady = "Download ready" +resumeTooltip = "Resume placement" +resume = "Resume placement" +pauseTooltip = "Pause placement" +pause = "Pause placement" +undo = "Undo" +redo = "Redo" [search] title = "Search PDF" diff --git a/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx b/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx index bc19a673c..238535717 100644 --- a/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx +++ b/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx @@ -1,5 +1,6 @@ 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'; interface TextInputWithFontProps { @@ -43,6 +44,7 @@ export const TextInputWithFont: React.FC = ({ colorLabel, onAnyChange }) => { + const { t } = useTranslation(); const [fontSizeInput, setFontSizeInput] = useState(fontSize.toString()); const fontSizeCombobox = useCombobox(); const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); @@ -221,7 +223,7 @@ export const TextInputWithFont: React.FC = ({ {onTextAlignChange && ( { + onChange={(value: string) => { onTextAlignChange(value as 'left' | 'center' | 'right'); onAnyChange?.(); }} diff --git a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx index 0b6c5df06..fa576fa20 100644 --- a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx +++ b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx @@ -126,9 +126,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA }), // Register pan plugin (depends on Viewport, InteractionManager) - keep disabled to prevent drag panning - createPluginRegistration(PanPluginPackage, { - defaultMode: 'disabled', - }), + createPluginRegistration(PanPluginPackage, {}), // Register zoom plugin with configuration createPluginRegistration(ZoomPluginPackage, { @@ -265,7 +263,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'highlight', name: 'Highlight', interaction: { exclusive: true, cursor: 'text', textSelection: true }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.HIGHLIGHT ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.HIGHLIGHT ? 10 : 0), defaults: { type: PdfAnnotationSubtype.HIGHLIGHT, color: '#ffd54f', @@ -281,7 +279,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'underline', name: 'Underline', interaction: { exclusive: true, cursor: 'text', textSelection: true }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.UNDERLINE ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.UNDERLINE ? 10 : 0), defaults: { type: PdfAnnotationSubtype.UNDERLINE, color: '#ffb300', @@ -297,7 +295,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'strikeout', name: 'Strikeout', interaction: { exclusive: true, cursor: 'text', textSelection: true }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.STRIKEOUT ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.STRIKEOUT ? 10 : 0), defaults: { type: PdfAnnotationSubtype.STRIKEOUT, color: '#e53935', @@ -313,7 +311,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'squiggly', name: 'Squiggly', interaction: { exclusive: true, cursor: 'text', textSelection: true }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.SQUIGGLY ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.SQUIGGLY ? 10 : 0), defaults: { type: PdfAnnotationSubtype.SQUIGGLY, color: '#00acc1', @@ -329,7 +327,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'ink', name: 'Pen', interaction: { exclusive: true, cursor: 'crosshair' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.INK ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK ? 10 : 0), defaults: { type: PdfAnnotationSubtype.INK, color: '#1f2933', @@ -348,7 +346,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'inkHighlighter', name: 'Ink Highlighter', interaction: { exclusive: true, cursor: 'crosshair' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.INK && annotation.color === '#ffd54f' ? 8 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK && annotation.color === '#ffd54f' ? 8 : 0), defaults: { type: PdfAnnotationSubtype.INK, color: '#ffd54f', @@ -367,7 +365,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'square', name: 'Square', interaction: { exclusive: true, cursor: 'crosshair' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.SQUARE ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.SQUARE ? 10 : 0), defaults: { type: PdfAnnotationSubtype.SQUARE, color: '#0000ff', // fill color (blue) @@ -391,7 +389,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'circle', name: 'Circle', interaction: { exclusive: true, cursor: 'crosshair' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.CIRCLE ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.CIRCLE ? 10 : 0), defaults: { type: PdfAnnotationSubtype.CIRCLE, color: '#0000ff', // fill color (blue) @@ -415,7 +413,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'line', name: 'Line', interaction: { exclusive: true, cursor: 'crosshair' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.LINE ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.LINE ? 10 : 0), defaults: { type: PdfAnnotationSubtype.LINE, color: '#1565c0', @@ -439,7 +437,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'lineArrow', name: 'Arrow', interaction: { exclusive: true, cursor: 'crosshair' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.LINE && (annotation.endStyle === 'ClosedArrow' || annotation.lineEndingStyles?.end === 'ClosedArrow') ? 9 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.LINE && (annotation.endStyle === 'ClosedArrow' || annotation.lineEndingStyles?.end === 'ClosedArrow') ? 9 : 0), defaults: { type: PdfAnnotationSubtype.LINE, color: '#1565c0', @@ -464,7 +462,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'polyline', name: 'Polyline', interaction: { exclusive: true, cursor: 'crosshair' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.POLYLINE ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.POLYLINE ? 10 : 0), defaults: { type: PdfAnnotationSubtype.POLYLINE, color: '#1565c0', @@ -485,7 +483,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'polygon', name: 'Polygon', interaction: { exclusive: true, cursor: 'crosshair' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.POLYGON ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.POLYGON ? 10 : 0), defaults: { type: PdfAnnotationSubtype.POLYGON, color: '#0000ff', // fill color (blue) @@ -508,7 +506,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'text', name: 'Text', interaction: { exclusive: true, cursor: 'text' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.FREETEXT ? 10 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.FREETEXT ? 10 : 0), defaults: { type: PdfAnnotationSubtype.FREETEXT, textColor: '#111111', @@ -528,7 +526,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'note', name: 'Note', interaction: { exclusive: true, cursor: 'pointer' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.FREETEXT ? 8 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.FREETEXT ? 8 : 0), defaults: { type: PdfAnnotationSubtype.FREETEXT, textColor: '#1b1b1b', @@ -552,7 +550,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedA id: 'stamp', name: 'Image Stamp', interaction: { exclusive: false, cursor: 'copy' }, - matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.STAMP ? 5 : 0), + matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.STAMP ? 5 : 0), defaults: { type: PdfAnnotationSubtype.STAMP, }, diff --git a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx index 9ef19dd71..5c5adb4e9 100644 --- a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx +++ b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useMemo, useState, useEffect, useCallback } from 'react'; import { ActionIcon, Popover } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { useViewer } from '@app/contexts/ViewerContext'; @@ -9,6 +9,9 @@ import { SearchInterface } from '@app/components/viewer/SearchInterface'; import ViewerAnnotationControls from '@app/components/shared/rightRail/ViewerAnnotationControls'; import { useSidebarContext } from '@app/contexts/SidebarContext'; import { useRightRailTooltipSide } from '@app/hooks/useRightRailTooltipSide'; +import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext'; +import { useNavigationState } from '@app/contexts/NavigationContext'; +import { BASE_PATH, withBasePath } from '@app/constants/app'; export function useViewerRightRailButtons() { const { t, i18n } = useTranslation(); @@ -16,6 +19,32 @@ export function useViewerRightRailButtons() { const [isPanning, setIsPanning] = useState(() => viewer.getPanState()?.isPanning ?? false); const { sidebarRefs } = useSidebarContext(); const { position: tooltipPosition } = useRightRailTooltipSide(sidebarRefs, 12); + const { handleToolSelect } = useToolWorkflow(); + const { selectedTool } = useNavigationState(); + + const stripBasePath = useCallback((path: string) => { + if (BASE_PATH && path.startsWith(BASE_PATH)) { + return path.slice(BASE_PATH.length) || '/'; + } + return path; + }, []); + + const isAnnotationsPath = useCallback(() => { + const cleanPath = stripBasePath(window.location.pathname).toLowerCase(); + return cleanPath === '/annotations' || cleanPath.endsWith('/annotations'); + }, [stripBasePath]); + + const [isAnnotationsActive, setIsAnnotationsActive] = useState(() => isAnnotationsPath()); + + useEffect(() => { + setIsAnnotationsActive(isAnnotationsPath()); + }, [selectedTool, isAnnotationsPath]); + + useEffect(() => { + const handlePopState = () => setIsAnnotationsActive(isAnnotationsPath()); + window.addEventListener('popstate', handlePopState); + return () => window.removeEventListener('popstate', handlePopState); + }, [isAnnotationsPath]); // Lift i18n labels out of memo for clarity const searchLabel = t('rightRail.search', 'Search PDF'); @@ -25,6 +54,7 @@ export function useViewerRightRailButtons() { const sidebarLabel = t('rightRail.toggleSidebar', 'Toggle Sidebar'); const bookmarkLabel = t('rightRail.toggleBookmarks', 'Toggle Bookmarks'); const printLabel = t('rightRail.print', 'Print PDF'); + const annotationsLabel = t('rightRail.annotations', 'Annotations'); const viewerButtons = useMemo(() => { return [ @@ -147,6 +177,36 @@ export function useViewerRightRailButtons() { viewer.printActions.print(); } }, + { + id: 'viewer-annotations', + tooltip: annotationsLabel, + ariaLabel: annotationsLabel, + section: 'top' as const, + order: 58, + render: ({ disabled }) => ( + + { + if (disabled || isAnnotationsActive) return; + const targetPath = withBasePath('/annotations'); + if (window.location.pathname !== targetPath) { + window.history.pushState(null, '', targetPath); + } + setIsAnnotationsActive(true); + handleToolSelect('annotate'); + }} + disabled={disabled || isAnnotationsActive} + aria-pressed={isAnnotationsActive} + style={isAnnotationsActive ? { backgroundColor: 'var(--right-rail-pan-active-bg)' } : undefined} + > + + + + ) + }, { id: 'viewer-annotation-controls', section: 'top' as const, @@ -156,7 +216,7 @@ export function useViewerRightRailButtons() { ) } ]; - }, [t, i18n.language, viewer, isPanning, searchLabel, panLabel, rotateLeftLabel, rotateRightLabel, sidebarLabel, bookmarkLabel, printLabel, tooltipPosition]); + }, [t, i18n.language, viewer, isPanning, searchLabel, panLabel, rotateLeftLabel, rotateRightLabel, sidebarLabel, bookmarkLabel, printLabel, tooltipPosition, annotationsLabel, isAnnotationsActive, handleToolSelect]); useRightRailButtons(viewerButtons); } diff --git a/frontend/src/core/components/viewer/viewerTypes.ts b/frontend/src/core/components/viewer/viewerTypes.ts index 87a3c7a1a..ac6647520 100644 --- a/frontend/src/core/components/viewer/viewerTypes.ts +++ b/frontend/src/core/components/viewer/viewerTypes.ts @@ -66,10 +66,12 @@ export type AnnotationSelection = unknown; export interface AnnotationToolOptions { color?: string; fillColor?: string; + strokeColor?: string; opacity?: number; strokeOpacity?: number; fillOpacity?: number; thickness?: number; + borderWidth?: number; fontSize?: number; fontFamily?: string; textAlign?: number; // 0 = Left, 1 = Center, 2 = Right diff --git a/frontend/src/core/data/useTranslatedToolRegistry.tsx b/frontend/src/core/data/useTranslatedToolRegistry.tsx index 997d4c527..e36317e6d 100644 --- a/frontend/src/core/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/core/data/useTranslatedToolRegistry.tsx @@ -253,7 +253,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog { component: Annotate, description: t('home.annotate.desc', 'Highlight, draw, add notes, and shapes directly in the viewer'), categoryId: ToolCategoryId.STANDARD_TOOLS, - subcategoryId: SubcategoryId.EDIT, + subcategoryId: SubcategoryId.GENERAL, workbench: 'viewer', operationConfig: signOperationConfig, automationSettings: null, diff --git a/frontend/src/core/tools/Annotate.tsx b/frontend/src/core/tools/Annotate.tsx index 1f7a118bb..7f861953f 100644 --- a/frontend/src/core/tools/Annotate.tsx +++ b/frontend/src/core/tools/Annotate.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState, useContext, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { Alert, Text, Group, ActionIcon, Stack, Divider, Slider, Box, Tooltip as MantineTooltip, Button, TextInput, Textarea, NumberInput, Tooltip, Paper } from '@mantine/core'; +import { Text, Group, ActionIcon, Stack, Slider, Box, Tooltip as MantineTooltip, Button, Textarea, Tooltip, Paper } from '@mantine/core'; import { alert as showToast, updateToast } from '@app/components/toast'; import { createToolFlow } from '@app/components/tools/shared/createToolFlow'; @@ -53,7 +53,7 @@ const Annotate = (_props: BaseToolProps) => { setSignatureConfig, setPlacementMode, placementPreviewSize, - activateSignaturePlacementMode, + activateSignaturePlacementMode: _activateSignaturePlacementMode, setPlacementPreviewSize, } = useSignature(); const viewerContext = useContext(ViewerContext); @@ -72,6 +72,8 @@ const Annotate = (_props: BaseToolProps) => { const [squigglyColor, setSquigglyColor] = useState('#00acc1'); const [squigglyOpacity, setSquigglyOpacity] = useState(100); const [textColor, setTextColor] = useState('#111111'); + const [textBackgroundColor, setTextBackgroundColor] = useState(''); + const [noteBackgroundColor, setNoteBackgroundColor] = useState('#ffd54f'); const [textSize, setTextSize] = useState(14); const [textAlignment, setTextAlignment] = useState<'left' | 'center' | 'right'>('left'); const [shapeStrokeColor, setShapeStrokeColor] = useState('#cf5b5b'); @@ -80,7 +82,9 @@ const Annotate = (_props: BaseToolProps) => { const [shapeStrokeOpacity, setShapeStrokeOpacity] = useState(50); const [shapeFillOpacity, setShapeFillOpacity] = useState(50); const [shapeThickness, setShapeThickness] = useState(1); - const [colorPickerTarget, setColorPickerTarget] = useState<'ink' | 'highlight' | 'underline' | 'strikeout' | 'squiggly' | 'text' | 'shapeStroke' | 'shapeFill' | null>(null); + const [colorPickerTarget, setColorPickerTarget] = useState< + 'ink' | 'highlight' | 'inkHighlighter' | 'underline' | 'strikeout' | 'squiggly' | 'text' | 'textBackground' | 'noteBackground' | 'shapeStroke' | 'shapeFill' | null + >(null); const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); const [selectedAnn, setSelectedAnn] = useState(null); const [selectedAnnId, setSelectedAnnId] = useState(null); @@ -174,16 +178,25 @@ const Annotate = (_props: BaseToolProps) => { return { color: strikeoutColor, opacity: strikeoutOpacity / 100, ...metadata }; case 'squiggly': return { color: squigglyColor, opacity: squigglyOpacity / 100, ...metadata }; - case 'text': + case 'text': { const textAlignNumber = textAlignment === 'left' ? 0 : textAlignment === 'center' ? 1 : 2; - return { color: textColor, fontSize: textSize, textAlign: textAlignNumber, ...metadata }; - case 'note': + return { + color: textColor, + fontSize: textSize, + textAlign: textAlignNumber, + ...(textBackgroundColor ? { fillColor: textBackgroundColor } : {}), + ...metadata, + }; + } + case 'note': { + const noteFillColor = noteBackgroundColor || 'transparent'; return { color: textColor, // text color - fillColor: highlightColor, // background color, shares highlight picker defaults + fillColor: noteFillColor, opacity: 1, ...metadata, }; + } case 'square': case 'circle': case 'polygon': @@ -217,7 +230,33 @@ const Annotate = (_props: BaseToolProps) => { default: return {}; } - }, [highlightColor, highlightOpacity, inkColor, inkWidth, freehandHighlighterWidth, underlineColor, underlineOpacity, strikeoutColor, strikeoutOpacity, squigglyColor, squigglyOpacity, textColor, textSize, textAlignment, shapeStrokeColor, shapeFillColor, shapeOpacity, shapeStrokeOpacity, shapeFillOpacity, shapeThickness, stampImageData, stampImageSize, cssToPdfSize]); + }, [ + highlightColor, + highlightOpacity, + inkColor, + inkWidth, + freehandHighlighterWidth, + underlineColor, + underlineOpacity, + strikeoutColor, + strikeoutOpacity, + squigglyColor, + squigglyOpacity, + textColor, + textSize, + textAlignment, + textBackgroundColor, + noteBackgroundColor, + shapeStrokeColor, + shapeFillColor, + shapeOpacity, + shapeStrokeOpacity, + shapeFillOpacity, + shapeThickness, + stampImageData, + stampImageSize, + cssToPdfSize, + ]); useEffect(() => { setToolAndWorkbench('annotate', 'viewer'); @@ -433,6 +472,7 @@ const Annotate = (_props: BaseToolProps) => { const applySelectionFromAnnotation = useCallback((ann: any | null) => { const annObject = ann?.object ?? ann ?? null; + const type = annObject?.type; const annId = annObject?.id ?? null; selectedAnnIdRef.current = annId; setSelectedAnnId(annId); @@ -444,8 +484,21 @@ const Annotate = (_props: BaseToolProps) => { if (annObject?.fontSize !== undefined) { setSelectedFontSize(annObject.fontSize ?? 14); } + if (type === 3) { + const derivedTool = deriveToolFromAnnotation(annObject); + const background = annObject?.backgroundColor as string | undefined; + if (annObject?.textColor) { + setTextColor(annObject.textColor); + } + if (derivedTool === 'note') { + if (background) { + setNoteBackgroundColor(background); + } + } else { + setTextBackgroundColor(background || ''); + } + } // Sync width properties based on annotation type - const type = annObject?.type; if (type === 15 && annObject?.strokeWidth !== undefined) { // Type 15 = INK, uses strokeWidth setInkWidth(annObject.strokeWidth ?? 2); @@ -536,7 +589,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: 'note', label: t('annotation.note', 'Note'), icon: 'sticky-note-2' }, { id: 'stamp', label: t('annotation.stamp', 'Add Image'), icon: 'add-photo-alternate' }, ]; @@ -550,12 +603,16 @@ const Annotate = (_props: BaseToolProps) => { : colorPickerTarget === 'strikeout' ? strikeoutColor : colorPickerTarget === 'squiggly' - ? squigglyColor - : colorPickerTarget === 'shapeStroke' - ? shapeStrokeColor - : colorPickerTarget === 'shapeFill' + ? squigglyColor + : colorPickerTarget === 'shapeStroke' + ? shapeStrokeColor + : colorPickerTarget === 'shapeFill' ? shapeFillColor - : textColor; + : colorPickerTarget === 'textBackground' + ? (textBackgroundColor || '#ffffff') + : colorPickerTarget === 'noteBackground' + ? (noteBackgroundColor || '#ffffff') + : textColor; const steps = useMemo(() => { if (selectedFiles.length === 0) return []; @@ -767,9 +824,72 @@ const Annotate = (_props: BaseToolProps) => { + + {t('annotation.backgroundColor', 'Background color')} + + { + setColorPickerTarget('textBackground'); + setIsColorPickerOpen(true); + }} + /> + + + )} + {activeTool === 'note' && ( + + {t('annotation.backgroundColor', 'Background color')} + + { + setColorPickerTarget('noteBackground'); + setIsColorPickerOpen(true); + }} + /> + + + + )} + {['square', 'circle', 'line', 'polygon'].includes(activeTool) && ( <> @@ -895,10 +1015,15 @@ const Annotate = (_props: BaseToolProps) => { // Type 3: Text box if (type === 3) { + const derivedTool = deriveToolFromAnnotation(selectedAnn.object); + const isNote = derivedTool === 'note'; + const selectedBackground = + selectedAnn.object?.backgroundColor ?? + (isNote ? noteBackgroundColor || '#ffffff' : textBackgroundColor || '#ffffff'); return ( - {t('annotation.editText', 'Edit Text Box')} + {isNote ? t('annotation.editNote', 'Edit Sticky Note') : t('annotation.editText', 'Edit Text Box')} {t('annotation.color', 'Color')} { }} /> + + {t('annotation.backgroundColor', 'Background color')} + + { + setColorPickerTarget(isNote ? 'noteBackground' : 'textBackground'); + setIsColorPickerOpen(true); + }} + /> + + +