mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-17 11:45:05 +02:00
Improve annotations (#5919)
* Text box/notes movement improvements ✅ * Fix the issue where hiding, then showing annotations looses progress ✅ * Fix the issue where hidig/showing annotations jumps you back up to the top of your open document ✅ * Support ctrl+c and ctrl+v and backspace to delete ✅ * Better handling when moving to different tool from annotate ✅ * Added a color picker eyedropper button ✅ * Auto-switch to Select after note/text placement, so users can quickly place and type ✅
This commit is contained in:
@@ -7,7 +7,7 @@ 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 type { AnnotationToolId } from '@app/components/viewer/viewerTypes';
|
||||
import type { AnnotationToolId, AnnotationEvent, AnnotationSelection, AnnotationRect } from '@app/components/viewer/viewerTypes';
|
||||
import { useAnnotationStyleState } from '@app/tools/annotate/useAnnotationStyleState';
|
||||
import { useAnnotationSelection } from '@app/tools/annotate/useAnnotationSelection';
|
||||
import { AnnotationPanel } from '@app/tools/annotate/AnnotationPanel';
|
||||
@@ -73,6 +73,8 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
setPlacementPreviewSize,
|
||||
} = useSignature();
|
||||
const viewerContext = useContext(ViewerContext);
|
||||
const viewerContextRef = useRef(viewerContext);
|
||||
useEffect(() => { viewerContextRef.current = viewerContext; }, [viewerContext]);
|
||||
const { getZoomState, registerImmediateZoomUpdate, applyChanges, activeFileIndex, panActions } = useViewer();
|
||||
|
||||
const [activeTool, setActiveTool] = useState<AnnotationToolId>('select');
|
||||
@@ -173,19 +175,34 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
}
|
||||
}, [applyChanges]);
|
||||
|
||||
// Deactivate all annotation tools when the Annotate component unmounts (e.g. switching to Sign tool).
|
||||
// The dep-change effect below only fires while mounted, so unmount needs its own cleanup.
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
annotationApiRef?.current?.deactivateTools?.();
|
||||
signatureApiRef?.current?.deactivateTools?.();
|
||||
setPlacementMode(false);
|
||||
viewerContextRef.current?.setAnnotationMode(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const isAnnotateActive = workbench === 'viewer' && selectedTool === 'annotate';
|
||||
if (wasAnnotateActiveRef.current && !isAnnotateActive) {
|
||||
annotationApiRef?.current?.deactivateTools?.();
|
||||
signatureApiRef?.current?.deactivateTools?.();
|
||||
setPlacementMode(false);
|
||||
viewerContext?.setAnnotationMode(false);
|
||||
} else if (!wasAnnotateActiveRef.current && isAnnotateActive) {
|
||||
// When entering annotate mode, activate the select tool by default
|
||||
// Also reset React state to match — EmbedPDF always starts at 'select' here
|
||||
setActiveTool('select');
|
||||
activeToolRef.current = 'select';
|
||||
const toolOptions = buildToolOptions('select');
|
||||
annotationApiRef?.current?.activateAnnotationTool?.('select', toolOptions);
|
||||
}
|
||||
wasAnnotateActiveRef.current = isAnnotateActive;
|
||||
}, [workbench, selectedTool, annotationApiRef, signatureApiRef, setPlacementMode, buildToolOptions]);
|
||||
}, [workbench, selectedTool, annotationApiRef, signatureApiRef, setPlacementMode, buildToolOptions, viewerContext]);
|
||||
|
||||
// Monitor history state for undo/redo availability
|
||||
useEffect(() => {
|
||||
@@ -323,23 +340,150 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
}
|
||||
}, [placementPreviewSize, activeTool, stampImageData, signatureApiRef, stampImageSize, cssToPdfSize, buildToolOptions]);
|
||||
|
||||
// Allow exiting multi-point tools with Escape (e.g., polyline)
|
||||
// Auto-switch to 'select' after placing a note or text annotation
|
||||
// EmbedPDF fires 'create' + committed:true when placement is finalised
|
||||
useEffect(() => {
|
||||
const unsubscribe = annotationApiRef?.current?.onAnnotationEvent?.((event: AnnotationEvent) => {
|
||||
if (event.type === 'create' && event.committed) {
|
||||
const toolId = activeToolRef.current;
|
||||
if (toolId === 'text' || toolId === 'note') {
|
||||
setActiveTool('select');
|
||||
activeToolRef.current = 'select';
|
||||
annotationApiRef?.current?.activateAnnotationTool?.('select');
|
||||
}
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
if (typeof unsubscribe === 'function') unsubscribe();
|
||||
};
|
||||
}, [annotationApiRef?.current]);
|
||||
|
||||
// Clipboard ref for copy/paste — no re-render needed
|
||||
const clipboardRef = useRef<{ pageIndex: number; annotation: Record<string, unknown> } | null>(null);
|
||||
|
||||
// Click-outside to blur FreeText/note inline editing so user can then drag the annotation.
|
||||
// When clicking the selection menu (e.g. Properties), only blur — do not deselect so the menu/popover can respond.
|
||||
useEffect(() => {
|
||||
const handleCapture = (e: MouseEvent) => {
|
||||
const active = document.activeElement as HTMLElement | null;
|
||||
if (!active?.isContentEditable) return;
|
||||
|
||||
const pageEl = active.closest('[data-page-index]') as HTMLElement | null;
|
||||
if (!pageEl) return;
|
||||
|
||||
const editingWrapper = active.parentElement;
|
||||
const target = e.target as Node;
|
||||
if (editingWrapper?.contains(target)) return;
|
||||
|
||||
active.blur();
|
||||
|
||||
const onSelectionMenu = (target as HTMLElement).closest?.('[data-annotation-selection-menu]');
|
||||
if (onSelectionMenu) return;
|
||||
|
||||
const pageParent = pageEl.parentElement;
|
||||
if (!pageParent) return;
|
||||
const synthetic = new PointerEvent('pointerdown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
pointerId: 1,
|
||||
pointerType: 'mouse',
|
||||
});
|
||||
pageParent.dispatchEvent(synthetic);
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleCapture, true);
|
||||
return () => document.removeEventListener('mousedown', handleCapture, true);
|
||||
}, []);
|
||||
|
||||
// Keyboard shortcuts: Escape (cancel drawing), Backspace/Delete (delete selected), Ctrl+C/V (copy/paste)
|
||||
useEffect(() => {
|
||||
const isInputFocused = () => {
|
||||
const el = document.activeElement;
|
||||
if (!el) return false;
|
||||
const tag = (el as HTMLElement).tagName;
|
||||
return tag === 'INPUT' || tag === 'TEXTAREA' || (el as HTMLElement).isContentEditable;
|
||||
};
|
||||
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key !== 'Escape') return;
|
||||
if (['polyline', 'polygon'].includes(activeTool)) {
|
||||
annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool));
|
||||
annotationApiRef?.current?.activateAnnotationTool?.(null as any);
|
||||
setTimeout(() => {
|
||||
annotationApiRef?.current?.activateAnnotationTool?.(activeTool, buildToolOptions(activeTool));
|
||||
}, 50);
|
||||
if (isInputFocused()) return;
|
||||
|
||||
// Backspace / Delete: delete selected annotation(s)
|
||||
if (e.key === 'Backspace' || e.key === 'Delete') {
|
||||
const multiSelected = annotationApiRef?.current?.getSelectedAnnotations?.();
|
||||
if (multiSelected && multiSelected.length > 0) {
|
||||
e.preventDefault();
|
||||
const toDelete = multiSelected.map((s: AnnotationSelection) => {
|
||||
const ann = s.object ?? s;
|
||||
return {
|
||||
pageIndex: ann.pageIndex ?? 0,
|
||||
id: ann.id ?? ann.uid ?? '',
|
||||
};
|
||||
}).filter((a: { id: string }) => a.id);
|
||||
if (toDelete.length > 0) {
|
||||
annotationApiRef?.current?.deleteAnnotations?.(toDelete);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const selected: AnnotationSelection | null = annotationApiRef?.current?.getSelectedAnnotation?.() ?? null;
|
||||
if (!selected) return;
|
||||
e.preventDefault();
|
||||
const pageIndex: number = selected.pageIndex ?? selected.object?.pageIndex ?? 0;
|
||||
const annotationId: string = selected.id ?? selected.object?.id ?? selected.uid ?? selected.object?.uid ?? '';
|
||||
if (annotationId != null) {
|
||||
annotationApiRef?.current?.deleteAnnotation?.(pageIndex, annotationId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+C: copy selected annotation
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
|
||||
const selected: AnnotationSelection | null = annotationApiRef?.current?.getSelectedAnnotation?.() ?? null;
|
||||
if (!selected) return;
|
||||
e.preventDefault();
|
||||
const ann = selected.object ?? selected;
|
||||
const pageIndex: number = ann.pageIndex ?? 0;
|
||||
clipboardRef.current = { pageIndex, annotation: { ...(ann as Record<string, unknown>) } };
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+V: paste copied annotation (offset by ~20 PDF pts)
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'v') {
|
||||
const clip = clipboardRef.current;
|
||||
if (!clip) return;
|
||||
e.preventDefault();
|
||||
const { pageIndex, annotation } = clip;
|
||||
const OFFSET = 20;
|
||||
const pasted: Record<string, unknown> = { ...annotation };
|
||||
// Assign a new id so EmbedPDF tracks the copy and delete works on it
|
||||
pasted.id = typeof crypto !== 'undefined' && crypto.randomUUID
|
||||
? crypto.randomUUID()
|
||||
: `paste-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
delete pasted.uid;
|
||||
// Remove appearance stream reference — the copy needs its own rendering
|
||||
delete pasted.appearanceModes;
|
||||
// Shift the EmbedPDF Rect: { origin: { x, y }, size: { width, height } }
|
||||
// PDF coords have y=0 at bottom, so down = smaller y; right = larger x
|
||||
if (pasted.rect && typeof pasted.rect === 'object') {
|
||||
const r = pasted.rect as AnnotationRect;
|
||||
if (r.origin && typeof r.origin === 'object') {
|
||||
pasted.rect = {
|
||||
...r,
|
||||
origin: { x: r.origin.x + OFFSET, y: r.origin.y - OFFSET },
|
||||
};
|
||||
}
|
||||
}
|
||||
annotationApiRef?.current?.createAnnotation?.(pageIndex, pasted);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handler);
|
||||
return () => window.removeEventListener('keydown', handler);
|
||||
}, [activeTool, buildToolOptions, signatureApiRef]);
|
||||
}, [buildToolOptions, annotationApiRef]);
|
||||
|
||||
const deriveToolFromAnnotation = useCallback((annotation: any): AnnotationToolId | undefined => {
|
||||
const deriveToolFromAnnotation = useCallback((annotation: AnnotationSelection | null | undefined): AnnotationToolId | undefined => {
|
||||
if (!annotation) return undefined;
|
||||
const customToolId = annotation.customData?.toolId || annotation.customData?.annotationToolId;
|
||||
if (isKnownAnnotationTool(customToolId)) {
|
||||
|
||||
@@ -7,7 +7,10 @@ import { ColorPicker, ColorSwatchButton } from '@app/components/annotation/share
|
||||
import { ImageUploader } from '@app/components/annotation/shared/ImageUploader';
|
||||
import { SuggestedToolsSection } from '@app/components/tools/shared/SuggestedToolsSection';
|
||||
import { DrawingControls } from '@app/components/annotation/shared/DrawingControls';
|
||||
import type { AnnotationToolId, AnnotationAPI } from '@app/components/viewer/viewerTypes';
|
||||
import type { AnnotationToolId, AnnotationAPI, SignatureAPI, AnnotationObject } from '@app/components/viewer/viewerTypes';
|
||||
import type { ViewerContextType } from '@app/contexts/ViewerContext';
|
||||
import type { SignParameters } from '@app/hooks/tools/sign/useSignParameters';
|
||||
import type { BuildToolOptionsExtras } from '@app/tools/annotate/useAnnotationStyleState';
|
||||
|
||||
interface StyleState {
|
||||
inkColor: string;
|
||||
@@ -59,7 +62,7 @@ interface StyleActions {
|
||||
setShapeThickness: (value: number) => void;
|
||||
}
|
||||
|
||||
type BuildToolOptionsFn = (toolId: AnnotationToolId, extras?: any) => Record<string, unknown>;
|
||||
type BuildToolOptionsFn = (toolId: AnnotationToolId, extras?: BuildToolOptionsExtras) => Record<string, unknown>;
|
||||
|
||||
type ColorTarget =
|
||||
| 'ink'
|
||||
@@ -82,17 +85,17 @@ interface AnnotationPanelProps {
|
||||
styleActions: StyleActions;
|
||||
getActiveColor: (tool: AnnotationToolId) => string;
|
||||
buildToolOptions: BuildToolOptionsFn;
|
||||
deriveToolFromAnnotation: (annotation: any) => AnnotationToolId | undefined;
|
||||
selectedAnn: any | null;
|
||||
deriveToolFromAnnotation: (annotation: AnnotationObject | null | undefined) => AnnotationToolId | undefined;
|
||||
selectedAnn: { object: AnnotationObject } | null;
|
||||
selectedTextDraft: string;
|
||||
setSelectedTextDraft: (text: string) => void;
|
||||
selectedFontSize: number;
|
||||
setSelectedFontSize: (size: number) => void;
|
||||
annotationApiRef: React.RefObject<AnnotationAPI | null>;
|
||||
signatureApiRef: React.RefObject<any>;
|
||||
viewerContext: any;
|
||||
signatureApiRef: React.RefObject<SignatureAPI | null>;
|
||||
viewerContext: ViewerContextType | null;
|
||||
setPlacementMode: (value: boolean) => void;
|
||||
setSignatureConfig: (config: any) => void;
|
||||
setSignatureConfig: (config: SignParameters | null) => void;
|
||||
computeStampDisplaySize: (natural: { width: number; height: number } | null) => { width: number; height: number };
|
||||
stampImageData?: string;
|
||||
setStampImageData: (value: string | undefined) => void;
|
||||
@@ -214,15 +217,18 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
|
||||
const activeColor = useMemo(() => colorPickerTarget ? getActiveColor(colorPickerTarget as AnnotationToolId) : '#000000', [colorPickerTarget, getActiveColor]);
|
||||
|
||||
const annotationsVisible = viewerContext?.isAnnotationsVisible ?? true;
|
||||
|
||||
const renderToolButtons = (tools: { id: AnnotationToolId; label: string; icon: string }[]) => (
|
||||
<Group gap="xs">
|
||||
{tools.map((tool) => (
|
||||
<MantineTooltip key={tool.id} label={tool.label} withArrow>
|
||||
<ActionIcon
|
||||
variant={activeTool === tool.id ? 'filled' : 'subtle'}
|
||||
color={activeTool === tool.id ? 'blue' : undefined}
|
||||
variant={activeTool === tool.id && annotationsVisible ? 'filled' : 'subtle'}
|
||||
color={activeTool === tool.id && annotationsVisible ? 'blue' : undefined}
|
||||
radius="md"
|
||||
onClick={() => activateAnnotationTool(tool.id)}
|
||||
disabled={!annotationsVisible}
|
||||
aria-label={tool.label}
|
||||
>
|
||||
<LocalIcon icon={tool.icon} width="1.25rem" height="1.25rem" />
|
||||
@@ -450,7 +456,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
if (selectedAnn?.object?.type === 3 && deriveToolFromAnnotation(selectedAnn.object) !== 'note') {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
selectedAnn.object.id as string,
|
||||
{ backgroundColor: 'transparent', fillColor: 'transparent' }
|
||||
);
|
||||
}
|
||||
@@ -484,7 +490,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
if (selectedAnn?.object?.type === 3 && deriveToolFromAnnotation(selectedAnn.object) === 'note') {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
selectedAnn.object.id as string,
|
||||
{ backgroundColor: 'transparent', fillColor: 'transparent' }
|
||||
);
|
||||
}
|
||||
@@ -579,25 +585,25 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool));
|
||||
}
|
||||
if (selectedAnn?.object?.id && (selectedAnn.object?.type === 9 || selectedAnn.object?.type === 15)) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { opacity: opacity / 100 });
|
||||
}
|
||||
} else if (colorPickerTarget === 'underline') {
|
||||
setUnderlineOpacity(opacity);
|
||||
annotationApiRef?.current?.setAnnotationStyle?.('underline', buildToolOptions('underline'));
|
||||
if (selectedAnn?.object?.id && selectedAnn.object?.type === 10) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { opacity: opacity / 100 });
|
||||
}
|
||||
} else if (colorPickerTarget === 'strikeout') {
|
||||
setStrikeoutOpacity(opacity);
|
||||
annotationApiRef?.current?.setAnnotationStyle?.('strikeout', buildToolOptions('strikeout'));
|
||||
if (selectedAnn?.object?.id && selectedAnn.object?.type === 12) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { opacity: opacity / 100 });
|
||||
}
|
||||
} else if (colorPickerTarget === 'squiggly') {
|
||||
setSquigglyOpacity(opacity);
|
||||
annotationApiRef?.current?.setAnnotationStyle?.('squiggly', buildToolOptions('squiggly'));
|
||||
if (selectedAnn?.object?.id && selectedAnn.object?.type === 11) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { opacity: opacity / 100 });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { opacity: opacity / 100 });
|
||||
}
|
||||
} else if (colorPickerTarget === 'shapeStroke') {
|
||||
setShapeStrokeOpacity(opacity);
|
||||
@@ -620,7 +626,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
annotationApiRef?.current?.setAnnotationStyle?.('ink', buildToolOptions('ink'));
|
||||
}
|
||||
if (selectedAnn?.object?.type === 15) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { color });
|
||||
}
|
||||
} else if (colorPickerTarget === 'highlight') {
|
||||
setHighlightColor(color);
|
||||
@@ -628,25 +634,25 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
annotationApiRef?.current?.setAnnotationStyle?.(activeTool, buildToolOptions(activeTool));
|
||||
}
|
||||
if (selectedAnn?.object?.type === 9 || selectedAnn?.object?.type === 15) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { color });
|
||||
}
|
||||
} else if (colorPickerTarget === 'underline') {
|
||||
setUnderlineColor(color);
|
||||
annotationApiRef?.current?.setAnnotationStyle?.('underline', buildToolOptions('underline'));
|
||||
if (selectedAnn?.object?.id) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { color });
|
||||
}
|
||||
} else if (colorPickerTarget === 'strikeout') {
|
||||
setStrikeoutColor(color);
|
||||
annotationApiRef?.current?.setAnnotationStyle?.('strikeout', buildToolOptions('strikeout'));
|
||||
if (selectedAnn?.object?.id) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { color });
|
||||
}
|
||||
} else if (colorPickerTarget === 'squiggly') {
|
||||
setSquigglyColor(color);
|
||||
annotationApiRef?.current?.setAnnotationStyle?.('squiggly', buildToolOptions('squiggly'));
|
||||
if (selectedAnn?.object?.id) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, { color });
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, { color });
|
||||
}
|
||||
} else if (colorPickerTarget === 'textBackground') {
|
||||
setTextBackgroundColor(color);
|
||||
@@ -656,7 +662,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
if (selectedAnn?.object?.type === 3 && deriveToolFromAnnotation(selectedAnn.object) !== 'note') {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
selectedAnn.object.id as string,
|
||||
{ backgroundColor: color, fillColor: color }
|
||||
);
|
||||
}
|
||||
@@ -668,7 +674,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
if (selectedAnn?.object?.type === 3 && deriveToolFromAnnotation(selectedAnn.object) === 'note') {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
selectedAnn.object.id as string,
|
||||
{ backgroundColor: color, fillColor: color }
|
||||
);
|
||||
}
|
||||
@@ -678,7 +684,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
annotationApiRef?.current?.setAnnotationStyle?.('text', buildToolOptions('text'));
|
||||
}
|
||||
if (selectedAnn?.object?.type === 3 || selectedAnn?.object?.type === 1) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, {
|
||||
textColor: color,
|
||||
color,
|
||||
});
|
||||
@@ -695,7 +701,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
annotationApiRef?.current?.setAnnotationStyle?.(styleTool, buildToolOptions(styleTool));
|
||||
}
|
||||
if (selectedAnn?.object?.id) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, {
|
||||
strokeColor: color,
|
||||
color: selectedAnn.object?.color ?? shapeFillColor,
|
||||
borderWidth: shapeThickness,
|
||||
@@ -709,7 +715,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
annotationApiRef?.current?.setAnnotationStyle?.(styleTool, buildToolOptions(styleTool));
|
||||
}
|
||||
if (selectedAnn?.object?.id) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id, {
|
||||
annotationApiRef?.current?.updateAnnotation?.(selectedAnn.object.pageIndex ?? 0, selectedAnn.object.id as string, {
|
||||
color,
|
||||
strokeColor: selectedAnn.object?.strokeColor ?? shapeStrokeColor,
|
||||
borderWidth: shapeThickness,
|
||||
@@ -726,8 +732,9 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
<Group gap="xs" wrap="nowrap" align="center">
|
||||
<Tooltip label={t('annotation.selectAndMove', 'Select and edit annotations')}>
|
||||
<ActionIcon
|
||||
variant={activeTool === 'select' ? 'filled' : 'default'}
|
||||
variant={activeTool === 'select' && annotationsVisible ? 'filled' : 'default'}
|
||||
size="lg"
|
||||
disabled={!annotationsVisible}
|
||||
onClick={() => {
|
||||
activateAnnotationTool('select');
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user