From e9a795eed2bd6c35477d147bcb177fca20f50a7b Mon Sep 17 00:00:00 2001 From: Reece Date: Wed, 17 Dec 2025 14:30:14 +0000 Subject: [PATCH] Refactor and select and move button --- .../public/locales/en-GB/translation.toml | 5 +- frontend/src/core/tools/Annotate.tsx | 1493 ++--------------- .../core/tools/annotate/AnnotationPanel.tsx | 1137 +++++++++++++ .../tools/annotate/useAnnotationSelection.ts | 154 ++ .../tools/annotate/useAnnotationStyleState.ts | 324 ++++ 5 files changed, 1738 insertions(+), 1375 deletions(-) create mode 100644 frontend/src/core/tools/annotate/AnnotationPanel.tsx create mode 100644 frontend/src/core/tools/annotate/useAnnotationSelection.ts create mode 100644 frontend/src/core/tools/annotate/useAnnotationStyleState.ts diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index 6f9d24e27..64e7bbbd8 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -4097,10 +4097,7 @@ 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" +selectAndMove = "Select and Move" undo = "Undo" redo = "Redo" diff --git a/frontend/src/core/tools/Annotate.tsx b/frontend/src/core/tools/Annotate.tsx index 7f861953f..a9f0a441a 100644 --- a/frontend/src/core/tools/Annotate.tsx +++ b/frontend/src/core/tools/Annotate.tsx @@ -1,19 +1,17 @@ -import { useEffect, useMemo, useState, useContext, useCallback, useRef } from 'react'; +import { useEffect, useState, useContext, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -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'; import { useNavigation } from '@app/contexts/NavigationContext'; -import { useFileSelection, useFileContext } from '@app/contexts/FileContext'; +import { useFileSelection } from '@app/contexts/FileContext'; import { BaseToolProps } from '@app/types/tool'; import { useSignature } from '@app/contexts/SignatureContext'; import { ViewerContext, useViewer } from '@app/contexts/ViewerContext'; -import { ColorPicker, ColorSwatchButton } from '@app/components/annotation/shared/ColorPicker'; -import { ImageUploader } from '@app/components/annotation/shared/ImageUploader'; -import LocalIcon from '@app/components/shared/LocalIcon'; import type { AnnotationToolId } from '@app/components/viewer/viewerTypes'; -import { SuggestedToolsSection } from '@app/components/tools/shared/SuggestedToolsSection'; +import { useAnnotationStyleState } from '@app/tools/annotate/useAnnotationStyleState'; +import { useAnnotationSelection } from '@app/tools/annotate/useAnnotationSelection'; +import { AnnotationPanel } from '@app/tools/annotate/AnnotationPanel'; const KNOWN_ANNOTATION_TOOLS: AnnotationToolId[] = [ 'select', @@ -43,7 +41,6 @@ const Annotate = (_props: BaseToolProps) => { const { t } = useTranslation(); const { setToolAndWorkbench, selectedTool, workbench } = useNavigation(); const { selectedFiles } = useFileSelection(); - const { selectors } = useFileContext(); const { signatureApiRef, annotationApiRef, @@ -53,50 +50,18 @@ const Annotate = (_props: BaseToolProps) => { setSignatureConfig, setPlacementMode, placementPreviewSize, - activateSignaturePlacementMode: _activateSignaturePlacementMode, setPlacementPreviewSize, } = useSignature(); const viewerContext = useContext(ViewerContext); const { getZoomState, registerImmediateZoomUpdate } = useViewer(); - const [activeTool, setActiveTool] = useState('highlight'); - const [inkColor, setInkColor] = useState('#1f2933'); - const [inkWidth, setInkWidth] = useState(2); - const [highlightColor, setHighlightColor] = useState('#ffd54f'); - const [highlightOpacity, setHighlightOpacity] = useState(60); - const [freehandHighlighterWidth, setFreehandHighlighterWidth] = useState(6); - const [underlineColor, setUnderlineColor] = useState('#ffb300'); - const [underlineOpacity, setUnderlineOpacity] = useState(100); - const [strikeoutColor, setStrikeoutColor] = useState('#e53935'); - const [strikeoutOpacity, setStrikeoutOpacity] = useState(100); - 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'); - const [shapeFillColor, setShapeFillColor] = useState('#0000ff'); - const [shapeOpacity, setShapeOpacity] = useState(50); - const [shapeStrokeOpacity, setShapeStrokeOpacity] = useState(50); - const [shapeFillOpacity, setShapeFillOpacity] = useState(50); - const [shapeThickness, setShapeThickness] = useState(1); - 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); - const selectedAnnIdRef = useRef(null); - const activeToolRef = useRef('highlight'); + const [activeTool, setActiveTool] = useState('select'); + const activeToolRef = useRef('select'); const wasAnnotateActiveRef = useRef(false); const [selectedTextDraft, setSelectedTextDraft] = useState(''); const [selectedFontSize, setSelectedFontSize] = useState(14); - const selectedUpdateTimer = useRef | null>(null); const [stampImageData, setStampImageData] = useState(); const [stampImageSize, setStampImageSize] = useState<{ width: number; height: number } | null>(null); - const [isAnnotationPaused, setIsAnnotationPaused] = useState(false); const [historyAvailability, setHistoryAvailability] = useState({ canUndo: false, canRedo: false }); const [isSavingCopy, setIsSavingCopy] = useState(false); const manualToolSwitch = useRef(false); @@ -121,10 +86,6 @@ const Annotate = (_props: BaseToolProps) => { activeToolRef.current = activeTool; }, [activeTool]); - useEffect(() => { - selectedAnnIdRef.current = selectedAnnId; - }, [selectedAnnId]); - // CSS to PDF size conversion accounting for zoom const cssToPdfSize = useCallback( (size: { width: number; height: number }) => { @@ -153,88 +114,18 @@ const Annotate = (_props: BaseToolProps) => { }; }, []); - const buildToolOptions = useCallback((toolId: AnnotationToolId, includeMetadata: boolean = true) => { - const metadata = includeMetadata ? { - customData: { - toolId, - annotationToolId: toolId, - source: 'annotate', - author: 'User', // Could be replaced with actual user name from auth context - createdAt: new Date().toISOString(), - modifiedAt: new Date().toISOString(), - } - } : {}; + const { + styleState, + styleActions, + buildToolOptions, + getActiveColor, + } = useAnnotationStyleState(cssToPdfSize); - switch (toolId) { - case 'ink': - return { color: inkColor, thickness: inkWidth, ...metadata }; - case 'inkHighlighter': - return { color: highlightColor, opacity: highlightOpacity / 100, thickness: freehandHighlighterWidth, ...metadata }; - case 'highlight': - return { color: highlightColor, opacity: highlightOpacity / 100, ...metadata }; - case 'underline': - return { color: underlineColor, opacity: underlineOpacity / 100, ...metadata }; - case 'strikeout': - return { color: strikeoutColor, opacity: strikeoutOpacity / 100, ...metadata }; - case 'squiggly': - return { color: squigglyColor, opacity: squigglyOpacity / 100, ...metadata }; - case 'text': { - const textAlignNumber = textAlignment === 'left' ? 0 : textAlignment === 'center' ? 1 : 2; - return { - color: textColor, - fontSize: textSize, - textAlign: textAlignNumber, - ...(textBackgroundColor ? { fillColor: textBackgroundColor } : {}), - ...metadata, - }; - } - case 'note': { - const noteFillColor = noteBackgroundColor || 'transparent'; - return { - color: textColor, // text color - fillColor: noteFillColor, - opacity: 1, - ...metadata, - }; - } - case 'square': - case 'circle': - case 'polygon': - return { - color: shapeFillColor, // fill color - strokeColor: shapeStrokeColor, // border color - opacity: shapeOpacity / 100, - strokeOpacity: shapeStrokeOpacity / 100, - fillOpacity: shapeFillOpacity / 100, - borderWidth: shapeThickness, - ...metadata, - }; - case 'line': - case 'polyline': - case 'lineArrow': - return { - color: shapeStrokeColor, - strokeColor: shapeStrokeColor, - opacity: shapeStrokeOpacity / 100, - borderWidth: shapeThickness, - ...metadata, - }; - case 'stamp': { - const pdfSize = stampImageSize ? cssToPdfSize(stampImageSize) : undefined; - return { - imageSrc: stampImageData, - ...(pdfSize ? { imageSize: pdfSize } : {}), - ...metadata, - }; - } - default: - return {}; - } - }, [ - highlightColor, - highlightOpacity, + const { inkColor, inkWidth, + highlightColor, + highlightOpacity, freehandHighlighterWidth, underlineColor, underlineOpacity, @@ -253,10 +144,32 @@ const Annotate = (_props: BaseToolProps) => { shapeStrokeOpacity, shapeFillOpacity, shapeThickness, - stampImageData, - stampImageSize, - cssToPdfSize, - ]); + } = styleState; + + const { + setInkColor, + setInkWidth, + setHighlightColor, + setHighlightOpacity, + setFreehandHighlighterWidth, + setUnderlineColor, + setUnderlineOpacity, + setStrikeoutColor, + setStrikeoutOpacity, + setSquigglyColor, + setSquigglyOpacity, + setTextColor, + setTextSize, + setTextAlignment, + setTextBackgroundColor, + setNoteBackgroundColor, + setShapeStrokeColor, + setShapeFillColor, + setShapeOpacity, + setShapeStrokeOpacity, + setShapeFillOpacity, + setShapeThickness, + } = styleActions; useEffect(() => { setToolAndWorkbench('annotate', 'viewer'); @@ -268,10 +181,13 @@ const Annotate = (_props: BaseToolProps) => { annotationApiRef?.current?.deactivateTools?.(); signatureApiRef?.current?.deactivateTools?.(); setPlacementMode(false); - setIsAnnotationPaused(true); + } else if (!wasAnnotateActiveRef.current && isAnnotateActive) { + // When entering annotate mode, activate the select tool by default + const toolOptions = buildToolOptions('select'); + annotationApiRef?.current?.activateAnnotationTool?.('select', toolOptions); } wasAnnotateActiveRef.current = isAnnotateActive; - }, [workbench, selectedTool, annotationApiRef, signatureApiRef, setPlacementMode]); + }, [workbench, selectedTool, annotationApiRef, signatureApiRef, setPlacementMode, buildToolOptions]); // Monitor history state for undo/redo availability useEffect(() => { @@ -300,8 +216,12 @@ const Annotate = (_props: BaseToolProps) => { if (viewerContext.isAnnotationMode) return; viewerContext.setAnnotationMode(true); - annotationApiRef?.current?.activateAnnotationTool?.(activeTool, buildToolOptions(activeTool)); - }, [viewerContext?.isAnnotationMode, signatureApiRef, activeTool, buildToolOptions]); + const toolOptions = + activeTool === 'stamp' + ? buildToolOptions('stamp', { stampImageData, stampImageSize }) + : buildToolOptions(activeTool); + annotationApiRef?.current?.activateAnnotationTool?.(activeTool, toolOptions); + }, [viewerContext?.isAnnotationMode, signatureApiRef, activeTool, buildToolOptions, stampImageData, stampImageSize]); const handleSaveCopy = useCallback(async () => { if (!viewerContext?.exportActions?.saveAsCopy) { @@ -382,19 +302,14 @@ const Annotate = (_props: BaseToolProps) => { // Change the tool setActiveTool(toolId); - const options = buildToolOptions(toolId); + const options = + toolId === 'stamp' + ? buildToolOptions('stamp', { stampImageData, stampImageSize }) + : buildToolOptions(toolId); // For stamp, apply the image if we have one - if (toolId === 'stamp' && stampImageData) { - const stampOptions = { - ...options, - imageSrc: stampImageData, - }; - annotationApiRef?.current?.setAnnotationStyle?.('stamp', stampOptions); - annotationApiRef?.current?.activateAnnotationTool?.('stamp', stampOptions); - } else { - annotationApiRef?.current?.activateAnnotationTool?.(toolId, options); - } + annotationApiRef?.current?.setAnnotationStyle?.(toolId, options); + annotationApiRef?.current?.activateAnnotationTool?.(toolId === 'stamp' ? 'stamp' : toolId, options); // Reset flag after a short delay setTimeout(() => { @@ -404,16 +319,13 @@ const Annotate = (_props: BaseToolProps) => { useEffect(() => { // push style updates to EmbedPDF when sliders/colors change - if (activeTool === 'stamp' && stampImageData) { - const options = buildToolOptions('stamp'); - annotationApiRef?.current?.setAnnotationStyle?.('stamp', { - ...options, - imageSrc: stampImageData, - }); + if (activeTool === 'stamp') { + const options = buildToolOptions('stamp', { stampImageData, stampImageSize }); + annotationApiRef?.current?.setAnnotationStyle?.('stamp', options); } else { annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool)); } - }, [activeTool, buildToolOptions, signatureApiRef, stampImageData]); + }, [activeTool, buildToolOptions, signatureApiRef, stampImageData, stampImageSize]); // Sync preview size from overlay to annotation engine useEffect(() => { @@ -422,12 +334,10 @@ const Annotate = (_props: BaseToolProps) => { // and apply the converted size to the stamp tool automatically if (activeTool === 'stamp' && stampImageData) { const size = placementPreviewSize ?? stampImageSize; - annotationApiRef?.current?.setAnnotationStyle?.('stamp', { - imageSrc: stampImageData, - ...(size ? { imageSize: cssToPdfSize(size) } : {}), - }); + const stampOptions = buildToolOptions('stamp', { stampImageData, stampImageSize: size ?? null }); + annotationApiRef?.current?.setAnnotationStyle?.('stamp', stampOptions); } - }, [placementPreviewSize, activeTool, stampImageData, signatureApiRef, stampImageSize, cssToPdfSize]); + }, [placementPreviewSize, activeTool, stampImageData, signatureApiRef, stampImageSize, cssToPdfSize, buildToolOptions]); // Allow exiting multi-point tools with Escape (e.g., polyline) useEffect(() => { @@ -470,1224 +380,65 @@ 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); - setSelectedAnn(ann || null); - - if (annObject?.contents !== undefined) { - setSelectedTextDraft(annObject.contents ?? ''); - } - 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 - if (type === 15 && annObject?.strokeWidth !== undefined) { - // Type 15 = INK, uses strokeWidth - setInkWidth(annObject.strokeWidth ?? 2); - } else if (type >= 4 && type <= 8 && annObject?.strokeWidth !== undefined) { - // Types 4-8 = Shapes (line, square, circle, polygon, polyline), use strokeWidth - setShapeThickness(annObject.strokeWidth ?? 1); - } - - const matchingTool = deriveToolFromAnnotation(annObject); - if (matchingTool && matchingTool !== activeToolRef.current && !manualToolSwitch.current) { - setActiveTool(matchingTool); - } - }, [deriveToolFromAnnotation]); - - // Track selection changes via events (fall back to light polling if events unavailable) - useEffect(() => { - const api = annotationApiRef?.current as any; - if (!api) return; - - if (typeof api.onAnnotationEvent === 'function') { - const handler = (event: any) => { - const ann = event?.annotation ?? event?.selectedAnnotation ?? null; - switch (event?.type) { - case 'select': - case 'selected': - applySelectionFromAnnotation(ann ?? api.getSelectedAnnotation?.()); - break; - case 'deselect': - case 'clearSelection': - applySelectionFromAnnotation(null); - break; - case 'delete': - case 'remove': - if (ann?.id && ann.id === selectedAnnIdRef.current) { - applySelectionFromAnnotation(null); - } - break; - case 'update': - case 'change': - if (selectedAnnIdRef.current) { - const current = api.getSelectedAnnotation?.(); - if (current) { - applySelectionFromAnnotation(current); - } - } - break; - default: - break; - } - }; - - const unsubscribe = api.onAnnotationEvent(handler); - return () => { - if (typeof unsubscribe === 'function') { - unsubscribe(); - } - }; - } - - // Fallback: slower polling to avoid heavy CPU churn - const interval = setInterval(() => { - const ann = api.getSelectedAnnotation?.(); - if ((ann?.object?.id ?? null) !== selectedAnnIdRef.current) { - applySelectionFromAnnotation(ann ?? null); - } - }, 350); - return () => clearInterval(interval); - }, [annotationApiRef, applySelectionFromAnnotation]); - - const textMarkupTools: { id: AnnotationToolId; label: string; icon: string }[] = [ - { id: 'highlight', label: t('annotation.highlight', 'Highlight'), icon: 'highlight' }, - { id: 'underline', label: t('annotation.underline', 'Underline'), icon: 'format-underlined' }, - { id: 'strikeout', label: t('annotation.strikeout', 'Strikeout'), icon: 'strikethrough-s' }, - { id: 'squiggly', label: t('annotation.squiggly', 'Squiggly'), icon: 'show-chart' }, - ]; - - const drawingTools: { id: AnnotationToolId; label: string; icon: string }[] = [ - { id: 'ink', label: t('annotation.pen', 'Pen'), icon: 'edit' }, - { id: 'inkHighlighter', label: t('annotation.freehandHighlighter', 'Freehand Highlighter'), icon: 'brush' }, - ]; - - const shapeTools: { id: AnnotationToolId; label: string; icon: string }[] = [ - { id: 'square', label: t('annotation.square', 'Square'), icon: 'crop-square' }, - { id: 'circle', label: t('annotation.circle', 'Circle'), icon: 'radio-button-unchecked' }, - { id: 'line', label: t('annotation.line', 'Line'), icon: 'show-chart' }, - { id: 'polygon', label: t('annotation.polygon', 'Polygon'), icon: 'change-history' }, - ]; - - 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' }, - ]; - - const activeColor = - colorPickerTarget === 'ink' - ? inkColor - : colorPickerTarget === 'highlight' || colorPickerTarget === 'inkHighlighter' - ? highlightColor - : colorPickerTarget === 'underline' - ? underlineColor - : colorPickerTarget === 'strikeout' - ? strikeoutColor - : colorPickerTarget === 'squiggly' - ? squigglyColor - : colorPickerTarget === 'shapeStroke' - ? shapeStrokeColor - : colorPickerTarget === 'shapeFill' - ? shapeFillColor - : colorPickerTarget === 'textBackground' - ? (textBackgroundColor || '#ffffff') - : colorPickerTarget === 'noteBackground' - ? (noteBackgroundColor || '#ffffff') - : textColor; - - const steps = useMemo(() => { - if (selectedFiles.length === 0) return []; - - const renderToolButtons = (tools: { id: AnnotationToolId; label: string; icon: string }[]) => ( - - {tools.map((tool) => ( - - activateAnnotationTool(tool.id)} - aria-label={tool.label} - > - - - - ))} - - ); - - const defaultStyleControls = ( - - - {activeTool === 'stamp' ? ( - <> - {t('annotation.stampSettings', 'Stamp Settings')} - { - if (file) { - try { - const dataUrl: string = await new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result as string); - reader.onerror = reject; - reader.readAsDataURL(file); - }); - - const naturalSize = await new Promise<{ width: number; height: number } | null>((resolve) => { - const img = new Image(); - img.onload = () => resolve({ width: img.naturalWidth || img.width, height: img.naturalHeight || img.height }); - img.onerror = () => resolve(null); - img.src = dataUrl; - }); - - const displaySize = computeStampDisplaySize(naturalSize); - setStampImageData(dataUrl); - setStampImageSize(displaySize); - setPlacementPreviewSize(displaySize); - - // Configure SignatureContext for placement preview - setSignatureConfig({ - signatureType: 'image', - signatureData: dataUrl, - }); - - setIsAnnotationPaused(false); - - // Activate placement mode with delay - setTimeout(() => { - viewerContext?.setAnnotationMode(true); - setPlacementMode(true); // This shows the preview overlay - const stampOptions = { - ...buildToolOptions('stamp'), - imageSrc: dataUrl, - ...(displaySize ? { imageSize: cssToPdfSize(displaySize) } : {}), - }; - annotationApiRef?.current?.setAnnotationStyle?.('stamp', stampOptions); - annotationApiRef?.current?.activateAnnotationTool?.('stamp', stampOptions); - }, 150); - } catch (err) { - console.error('Failed to load stamp image', err); - } - } else { - setStampImageData(undefined); - setStampImageSize(null); - setPlacementMode(false); - setSignatureConfig(null); - setPlacementPreviewSize(null); - } - }} - disabled={false} - /> - {stampImageData && ( - - {t('annotation.imagePreview', 'Preview')} - Stamp preview - - )} - - ) : ( - <> - {t('annotation.settings', 'Settings')} - - - - {['square', 'circle', 'polygon'].includes(activeTool) - ? t('annotation.strokeColor', 'Stroke Color') - : t('annotation.color', 'Color') - } - - { - const target = - activeTool === 'ink' - ? 'ink' - : activeTool === 'highlight' || activeTool === 'inkHighlighter' - ? 'highlight' - : activeTool === 'underline' - ? 'underline' - : activeTool === 'strikeout' - ? 'strikeout' - : activeTool === 'squiggly' - ? 'squiggly' - : ['square', 'circle', 'line', 'polygon'].includes(activeTool) - ? 'shapeStroke' - : 'text'; - setColorPickerTarget(target); - setIsColorPickerOpen(true); - }} - /> - - {['square', 'circle', 'polygon'].includes(activeTool) && ( - - {t('annotation.fillColor', 'Fill Color')} - { - setColorPickerTarget('shapeFill'); - setIsColorPickerOpen(true); - }} - /> - - )} - - - {activeTool === 'ink' && ( - - {t('annotation.strokeWidth', 'Width')} - - - )} - - {(activeTool === 'highlight' || activeTool === 'inkHighlighter') && ( - - {t('annotation.opacity', 'Opacity')} - - - )} - - {activeTool === 'inkHighlighter' && ( - - {t('annotation.strokeWidth', 'Width')} - - - )} - - {activeTool === 'text' && ( - <> - - {t('annotation.fontSize', 'Font size')} - - - - {t('annotation.textAlignment', 'Text Alignment')} - - setTextAlignment('left')} - size="md" - > - - - setTextAlignment('center')} - size="md" - > - - - setTextAlignment('right')} - size="md" - > - - - - - - {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) && ( - <> - - {t('annotation.opacity', 'Opacity')} - { - setShapeOpacity(value); - setShapeStrokeOpacity(value); - setShapeFillOpacity(value); - }} /> - - - {activeTool === 'line' ? ( - <> - {t('annotation.strokeWidth', 'Width')} - - - ) : ( - - - {t('annotation.strokeWidth', 'Stroke')} - - - - - )} - - - )} - - )} - - - ); - - const selectedAnnotationControls = selectedAnn && (() => { - const type = selectedAnn.object?.type; - - // Type 9: Highlight, Type 10: Underline, Type 11: Squiggly, Type 12: Strikeout - if ([9, 10, 11, 12].includes(type)) { - return ( - - - {t('annotation.editTextMarkup', 'Edit Text Markup')} - - {t('annotation.color', 'Color')} - { - setColorPickerTarget('highlight'); - setIsColorPickerOpen(true); - }} - /> - - - {t('annotation.opacity', 'Opacity')} - { - annotationApiRef?.current?.updateAnnotation?.( - selectedAnn.object?.pageIndex ?? 0, - selectedAnn.object?.id, - { opacity: value / 100 } - ); - }} - /> - - - - ); - } - - // Type 15: Ink (pen) - if (type === 15) { - return ( - - - {t('annotation.editInk', 'Edit Pen')} - - {t('annotation.color', 'Color')} - { - setColorPickerTarget('ink'); - setIsColorPickerOpen(true); - }} - /> - - - {t('annotation.strokeWidth', 'Width')} - { - annotationApiRef?.current?.updateAnnotation?.( - selectedAnn.object?.pageIndex ?? 0, - selectedAnn.object?.id, - { - strokeWidth: value, - } - ); - setInkWidth(value); - }} - /> - - - - ); - } - - // 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 ( - - - {isNote ? t('annotation.editNote', 'Edit Sticky Note') : t('annotation.editText', 'Edit Text Box')} - - {t('annotation.color', 'Color')} - { - setColorPickerTarget('text'); - setIsColorPickerOpen(true); - }} - /> - - - {t('annotation.backgroundColor', 'Background color')} - - { - setColorPickerTarget(isNote ? 'noteBackground' : 'textBackground'); - setIsColorPickerOpen(true); - }} - /> - - - -