mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-17 11:45:05 +02:00
Chore/v2/improve annotation UI (#5724)
This commit is contained in:
@@ -372,7 +372,6 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
annotationApiRef,
|
||||
deriveToolFromAnnotation,
|
||||
activeToolRef,
|
||||
manualToolSwitch,
|
||||
setActiveTool,
|
||||
setSelectedTextDraft,
|
||||
setSelectedFontSize,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import type React 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 { Text, Group, ActionIcon, Stack, Slider, Box, Tooltip as MantineTooltip, Button, Tooltip, Paper } from '@mantine/core';
|
||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
import { ColorPicker, ColorSwatchButton } from '@app/components/annotation/shared/ColorPicker';
|
||||
import { ImageUploader } from '@app/components/annotation/shared/ImageUploader';
|
||||
@@ -111,7 +111,6 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const [colorPickerTarget, setColorPickerTarget] = useState<ColorTarget>(null);
|
||||
const [isColorPickerOpen, setIsColorPickerOpen] = useState(false);
|
||||
const selectedUpdateTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const {
|
||||
activeTool,
|
||||
@@ -122,10 +121,6 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
buildToolOptions,
|
||||
deriveToolFromAnnotation,
|
||||
selectedAnn,
|
||||
selectedTextDraft,
|
||||
setSelectedTextDraft,
|
||||
selectedFontSize,
|
||||
setSelectedFontSize,
|
||||
annotationApiRef,
|
||||
viewerContext,
|
||||
setPlacementMode,
|
||||
@@ -549,512 +544,6 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
</Paper>
|
||||
);
|
||||
|
||||
const selectedDerivedTool = selectedAnn?.object ? deriveToolFromAnnotation(selectedAnn.object) : undefined;
|
||||
|
||||
const selectedAnnotationControls = selectedAnn && (() => {
|
||||
const rawType = selectedAnn.object?.type;
|
||||
const toolId = selectedDerivedTool ?? deriveToolFromAnnotation(selectedAnn.object);
|
||||
const derivedType =
|
||||
toolId === 'highlight' ? 9
|
||||
: toolId === 'underline' ? 10
|
||||
: toolId === 'squiggly' ? 11
|
||||
: toolId === 'strikeout' ? 12
|
||||
: toolId === 'line' ? 4
|
||||
: toolId === 'square' ? 5
|
||||
: toolId === 'circle' ? 6
|
||||
: toolId === 'polygon' ? 7
|
||||
: toolId === 'polyline' ? 8
|
||||
: toolId === 'text' ? 3
|
||||
: toolId === 'note' ? 3
|
||||
: toolId === 'stamp' ? 13
|
||||
: toolId === 'ink' ? 15
|
||||
: undefined;
|
||||
const type = typeof rawType === 'number' ? rawType : derivedType;
|
||||
|
||||
if (toolId && ['highlight', 'underline', 'strikeout', 'squiggly'].includes(toolId)) {
|
||||
return (
|
||||
<Paper withBorder p="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>{t('annotation.editTextMarkup', 'Edit Text Markup')}</Text>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||
<ColorSwatchButton
|
||||
color={selectedAnn.object?.color ?? highlightColor}
|
||||
size={28}
|
||||
onClick={() => {
|
||||
setColorPickerTarget('highlight');
|
||||
setIsColorPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.opacity', 'Opacity')}</Text>
|
||||
<Slider
|
||||
min={10}
|
||||
max={100}
|
||||
value={Math.round(((selectedAnn.object?.opacity ?? 1) * 100) || 100)}
|
||||
onChange={(value) => {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ opacity: value / 100 }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 15 || toolId === 'inkHighlighter' || toolId === 'ink') {
|
||||
const isHighlighter = toolId === 'inkHighlighter';
|
||||
const thicknessValue =
|
||||
selectedAnn.object?.strokeWidth ??
|
||||
selectedAnn.object?.borderWidth ??
|
||||
selectedAnn.object?.lineWidth ??
|
||||
selectedAnn.object?.thickness ??
|
||||
(isHighlighter ? freehandHighlighterWidth : inkWidth);
|
||||
const colorValue = selectedAnn.object?.color ?? (isHighlighter ? highlightColor : inkColor);
|
||||
const opacityValue = Math.round(((selectedAnn.object?.opacity ?? 1) * 100) || (isHighlighter ? highlightOpacity : 100));
|
||||
return (
|
||||
<Paper withBorder p="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>
|
||||
{isHighlighter ? t('annotation.freehandHighlighter', 'Freehand Highlighter') : t('annotation.editInk', 'Edit Pen')}
|
||||
</Text>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||
<ColorSwatchButton
|
||||
color={colorValue}
|
||||
size={28}
|
||||
onClick={() => {
|
||||
setColorPickerTarget(isHighlighter ? 'highlight' : 'ink');
|
||||
setIsColorPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{isHighlighter && (
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.opacity', 'Opacity')}</Text>
|
||||
<Slider
|
||||
min={10}
|
||||
max={100}
|
||||
value={opacityValue}
|
||||
onChange={(value) => {
|
||||
setHighlightOpacity(value);
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ opacity: value / 100 }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.strokeWidth', 'Width')}</Text>
|
||||
<Slider
|
||||
min={1}
|
||||
max={isHighlighter ? 20 : 12}
|
||||
value={thicknessValue}
|
||||
onChange={(value) => {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{
|
||||
strokeWidth: value,
|
||||
borderWidth: value,
|
||||
lineWidth: value,
|
||||
thickness: value,
|
||||
}
|
||||
);
|
||||
if (isHighlighter) {
|
||||
setFreehandHighlighterWidth?.(value);
|
||||
} else {
|
||||
setInkWidth(value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 3 || toolId === 'text' || toolId === 'note') {
|
||||
const isNote = toolId === 'note';
|
||||
const selectedBackground =
|
||||
selectedAnn.object?.backgroundColor ??
|
||||
(isNote ? noteBackgroundColor || '#ffffff' : textBackgroundColor || '#ffffff');
|
||||
const alignValue = selectedAnn.object?.textAlign;
|
||||
const currentAlign =
|
||||
typeof alignValue === 'number'
|
||||
? alignValue === 1
|
||||
? 'center'
|
||||
: alignValue === 2
|
||||
? 'right'
|
||||
: 'left'
|
||||
: alignValue === 'center'
|
||||
? 'center'
|
||||
: alignValue === 'right'
|
||||
? 'right'
|
||||
: 'left';
|
||||
|
||||
return (
|
||||
<Paper withBorder p="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>{isNote ? t('annotation.editNote', 'Edit Sticky Note') : t('annotation.editText', 'Edit Text Box')}</Text>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||
<ColorSwatchButton
|
||||
color={selectedAnn.object?.textColor ?? selectedAnn.object?.color ?? textColor}
|
||||
size={28}
|
||||
onClick={() => {
|
||||
setColorPickerTarget('text');
|
||||
setIsColorPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.backgroundColor', 'Background color')}</Text>
|
||||
<Group gap="xs" align="center">
|
||||
<ColorSwatchButton
|
||||
color={selectedBackground}
|
||||
size={28}
|
||||
onClick={() => {
|
||||
setColorPickerTarget(isNote ? 'noteBackground' : 'textBackground');
|
||||
setIsColorPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
variant={selectedAnn.object?.backgroundColor ? 'light' : 'default'}
|
||||
onClick={() => {
|
||||
if (isNote) {
|
||||
setNoteBackgroundColor('');
|
||||
} else {
|
||||
setTextBackgroundColor('');
|
||||
}
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ backgroundColor: 'transparent', fillColor: 'transparent' }
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t('annotation.clearBackground', 'Remove background')}
|
||||
</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
<Textarea
|
||||
label={t('annotation.text', 'Text')}
|
||||
value={selectedTextDraft}
|
||||
minRows={3}
|
||||
maxRows={8}
|
||||
autosize
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const target = e.currentTarget;
|
||||
const start = target.selectionStart;
|
||||
const end = target.selectionEnd;
|
||||
const val = selectedTextDraft;
|
||||
const newVal = val.substring(0, start) + '\r\n' + val.substring(end);
|
||||
setSelectedTextDraft(newVal);
|
||||
setTimeout(() => {
|
||||
target.selectionStart = target.selectionEnd = start + 2;
|
||||
}, 0);
|
||||
if (selectedUpdateTimer.current) {
|
||||
clearTimeout(selectedUpdateTimer.current);
|
||||
}
|
||||
selectedUpdateTimer.current = setTimeout(() => {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ contents: newVal, textColor: selectedAnn.object?.textColor ?? textColor }
|
||||
);
|
||||
}, 120);
|
||||
}
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
setSelectedTextDraft(val);
|
||||
if (selectedUpdateTimer.current) {
|
||||
clearTimeout(selectedUpdateTimer.current);
|
||||
}
|
||||
selectedUpdateTimer.current = setTimeout(() => {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ contents: val, textColor: selectedAnn.object?.textColor ?? textColor }
|
||||
);
|
||||
}, 120);
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.fontSize', 'Font size')}</Text>
|
||||
<Slider
|
||||
min={8}
|
||||
max={32}
|
||||
value={selectedFontSize}
|
||||
onChange={(size) => {
|
||||
setSelectedFontSize(size);
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ fontSize: size }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.textAlignment', 'Text Alignment')}</Text>
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant={currentAlign === 'left' ? 'filled' : 'default'}
|
||||
onClick={() => {
|
||||
setTextAlignment('left');
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ textAlign: 0 }
|
||||
);
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
<LocalIcon icon="format-align-left" width={18} height={18} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={currentAlign === 'center' ? 'filled' : 'default'}
|
||||
onClick={() => {
|
||||
setTextAlignment('center');
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ textAlign: 1 }
|
||||
);
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
<LocalIcon icon="format-align-center" width={18} height={18} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={currentAlign === 'right' ? 'filled' : 'default'}
|
||||
onClick={() => {
|
||||
setTextAlignment('right');
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ textAlign: 2 }
|
||||
);
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
<LocalIcon icon="format-align-right" width={18} height={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 13 || toolId === 'stamp') {
|
||||
const imageSrc = selectedAnn.object?.imageSrc || selectedAnn.object?.data || selectedAnn.object?.url;
|
||||
return (
|
||||
<Paper withBorder p="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>{t('annotation.stamp', 'Add Image')}</Text>
|
||||
{imageSrc ? (
|
||||
<Stack gap="xs">
|
||||
<Text size="xs" c="dimmed">{t('annotation.imagePreview', 'Preview')}</Text>
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={t('annotation.stamp', 'Add Image')}
|
||||
style={{ maxWidth: '100%', maxHeight: '180px', objectFit: 'contain', border: '1px solid #ccc', borderRadius: '4px' }}
|
||||
/>
|
||||
</Stack>
|
||||
) : (
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('annotation.unsupportedType', 'This annotation type is not fully supported for editing.')}
|
||||
</Text>
|
||||
)}
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('annotation.editStampHint', 'To change the image, delete this stamp and add a new one.')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
if ((type !== undefined && [4, 8].includes(type)) || toolId === 'line' || toolId === 'polyline') {
|
||||
return (
|
||||
<Paper withBorder p="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>{t('annotation.editLine', 'Edit Line')}</Text>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||
<ColorSwatchButton
|
||||
color={selectedAnn.object?.strokeColor ?? shapeStrokeColor}
|
||||
size={28}
|
||||
onClick={() => {
|
||||
setColorPickerTarget('shapeStroke');
|
||||
setIsColorPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.opacity', 'Opacity')}</Text>
|
||||
<Slider
|
||||
min={10}
|
||||
max={100}
|
||||
value={Math.round(((selectedAnn.object?.opacity ?? 1) * 100) || 100)}
|
||||
onChange={(value) => {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ opacity: value / 100 }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.strokeWidth', 'Width')}</Text>
|
||||
<Slider
|
||||
min={1}
|
||||
max={12}
|
||||
value={selectedAnn.object?.borderWidth ?? shapeThickness}
|
||||
onChange={(value) => {
|
||||
annotationApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{
|
||||
borderWidth: value,
|
||||
strokeWidth: value,
|
||||
lineWidth: value,
|
||||
}
|
||||
);
|
||||
setShapeThickness(value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
if ((type !== undefined && [5, 6, 7].includes(type)) || toolId === 'square' || toolId === 'circle' || toolId === 'polygon') {
|
||||
const shapeName = type === 5 ? 'Square' : type === 6 ? 'Circle' : 'Polygon';
|
||||
const strokeColorValue = selectedAnn.object?.strokeColor ?? shapeStrokeColor;
|
||||
const fillColorValue = selectedAnn.object?.color ?? shapeFillColor;
|
||||
const opacityValue = Math.round(((selectedAnn.object?.opacity ?? shapeOpacity / 100) * 100) || 100);
|
||||
const pageIndex = selectedAnn.object?.pageIndex ?? 0;
|
||||
const annId = selectedAnn.object?.id;
|
||||
return (
|
||||
<Paper withBorder p="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>{t(`annotation.edit${shapeName}`, `Edit ${shapeName}`)}</Text>
|
||||
<Group gap="md">
|
||||
<Stack gap={4} align="center">
|
||||
<Text size="xs" c="dimmed">{t('annotation.strokeColor', 'Stroke Color')}</Text>
|
||||
<ColorSwatchButton
|
||||
color={strokeColorValue}
|
||||
size={28}
|
||||
onClick={() => {
|
||||
setColorPickerTarget('shapeStroke');
|
||||
setIsColorPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack gap={4} align="center">
|
||||
<Text size="xs" c="dimmed">{t('annotation.fillColor', 'Fill Color')}</Text>
|
||||
<ColorSwatchButton
|
||||
color={fillColorValue}
|
||||
size={28}
|
||||
onClick={() => {
|
||||
setColorPickerTarget('shapeFill');
|
||||
setIsColorPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.opacity', 'Opacity')}</Text>
|
||||
<Slider
|
||||
min={10}
|
||||
max={100}
|
||||
value={opacityValue}
|
||||
onChange={(value) => {
|
||||
setShapeOpacity(value);
|
||||
setShapeStrokeOpacity(value);
|
||||
setShapeFillOpacity(value);
|
||||
if (annId) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(pageIndex, annId, {
|
||||
opacity: value / 100,
|
||||
strokeOpacity: value / 100,
|
||||
fillOpacity: value / 100,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.strokeWidth', 'Stroke')}</Text>
|
||||
<Slider
|
||||
min={0}
|
||||
max={12}
|
||||
value={selectedAnn.object?.borderWidth ?? shapeThickness}
|
||||
onChange={(value) => {
|
||||
if (annId) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(pageIndex, annId, {
|
||||
borderWidth: value,
|
||||
strokeWidth: value,
|
||||
lineWidth: value,
|
||||
});
|
||||
}
|
||||
setShapeThickness(value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button
|
||||
size="xs"
|
||||
variant={(selectedAnn.object?.borderWidth ?? shapeThickness) === 0 ? 'filled' : 'light'}
|
||||
onClick={() => {
|
||||
const newValue = (selectedAnn.object?.borderWidth ?? shapeThickness) === 0 ? 1 : 0;
|
||||
if (annId) {
|
||||
annotationApiRef?.current?.updateAnnotation?.(pageIndex, annId, {
|
||||
borderWidth: newValue,
|
||||
strokeWidth: newValue,
|
||||
lineWidth: newValue,
|
||||
});
|
||||
}
|
||||
setShapeThickness(newValue);
|
||||
}}
|
||||
>
|
||||
{(selectedAnn.object?.borderWidth ?? shapeThickness) === 0
|
||||
? t('annotation.borderOff', 'Border: Off')
|
||||
: t('annotation.borderOn', 'Border: On')
|
||||
}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper withBorder p="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>{t('annotation.editSelected', 'Edit Annotation')}</Text>
|
||||
<Text size="xs" c="dimmed">{t('annotation.unsupportedType', 'This annotation type is not fully supported for editing.')}</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
})();
|
||||
|
||||
const colorPickerComponent = (
|
||||
<ColorPicker
|
||||
isOpen={isColorPickerOpen}
|
||||
@@ -1287,11 +776,7 @@ export function AnnotationPanel(props: AnnotationPanelProps) {
|
||||
{renderToolButtons(otherTools)}
|
||||
</Box>
|
||||
|
||||
{activeTool !== 'select' && defaultStyleControls}
|
||||
|
||||
{activeTool === 'select' && selectedAnn && selectedAnnotationControls}
|
||||
|
||||
{activeTool === 'select' && !selectedAnn && defaultStyleControls}
|
||||
{activeTool === 'stamp' && defaultStyleControls}
|
||||
|
||||
{colorPickerComponent}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ interface UseAnnotationSelectionParams {
|
||||
annotationApiRef: React.RefObject<AnnotationAPI | null>;
|
||||
deriveToolFromAnnotation: (annotation: any) => AnnotationToolId | undefined;
|
||||
activeToolRef: React.MutableRefObject<AnnotationToolId>;
|
||||
manualToolSwitch: React.MutableRefObject<boolean>;
|
||||
setActiveTool: (toolId: AnnotationToolId) => void;
|
||||
setSelectedTextDraft: (text: string) => void;
|
||||
setSelectedFontSize: (size: number) => void;
|
||||
@@ -34,6 +33,7 @@ interface UseAnnotationSelectionParams {
|
||||
|
||||
const MARKUP_TOOL_IDS = ['highlight', 'underline', 'strikeout', 'squiggly'] as const;
|
||||
const DRAWING_TOOL_IDS = ['ink', 'inkHighlighter'] as const;
|
||||
const STAY_ACTIVE_TOOL_IDS = [...MARKUP_TOOL_IDS, ...DRAWING_TOOL_IDS] as const;
|
||||
|
||||
const isTextMarkupAnnotation = (annotation: any): boolean => {
|
||||
const toolId =
|
||||
@@ -55,6 +55,9 @@ const isTextMarkupAnnotation = (annotation: any): boolean => {
|
||||
};
|
||||
|
||||
const shouldStayOnPlacementTool = (annotation: any, derivedTool?: string | null | undefined): boolean => {
|
||||
// Text markup tools (highlight, underline, strikeout, squiggly) and drawing tools (ink, inkHighlighter) stay active
|
||||
// All other tools switch to select mode after placement
|
||||
|
||||
const toolId =
|
||||
derivedTool ||
|
||||
annotation?.customData?.annotationToolId ||
|
||||
@@ -62,12 +65,17 @@ const shouldStayOnPlacementTool = (annotation: any, derivedTool?: string | null
|
||||
annotation?.object?.customData?.annotationToolId ||
|
||||
annotation?.object?.customData?.toolId;
|
||||
|
||||
if (toolId && (MARKUP_TOOL_IDS.includes(toolId as any) || DRAWING_TOOL_IDS.includes(toolId as any))) {
|
||||
// Check if it's a tool that should stay active
|
||||
if (toolId && STAY_ACTIVE_TOOL_IDS.includes(toolId as any)) {
|
||||
return true;
|
||||
}
|
||||
const type = annotation?.type ?? annotation?.object?.type;
|
||||
if (typeof type === 'number' && type === 15) return true; // ink family
|
||||
if (isTextMarkupAnnotation(annotation)) return true;
|
||||
|
||||
// Check if it's a markup annotation by type/subtype
|
||||
if (isTextMarkupAnnotation(annotation)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// All other tools (text, note, shapes, lines, stamps) switch to select
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -75,7 +83,6 @@ export function useAnnotationSelection({
|
||||
annotationApiRef,
|
||||
deriveToolFromAnnotation,
|
||||
activeToolRef,
|
||||
manualToolSwitch,
|
||||
setActiveTool,
|
||||
setSelectedTextDraft,
|
||||
setSelectedFontSize,
|
||||
@@ -226,8 +233,8 @@ export function useAnnotationSelection({
|
||||
},
|
||||
[
|
||||
activeToolRef,
|
||||
annotationApiRef,
|
||||
deriveToolFromAnnotation,
|
||||
manualToolSwitch,
|
||||
setActiveTool,
|
||||
setInkWidth,
|
||||
setNoteBackgroundColor,
|
||||
@@ -252,7 +259,6 @@ export function useAnnotationSelection({
|
||||
setShapeFillOpacity,
|
||||
setTextAlignment,
|
||||
setFreehandHighlighterWidth,
|
||||
shouldStayOnPlacementTool,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -304,9 +310,7 @@ export function useAnnotationSelection({
|
||||
const tool =
|
||||
deriveToolFromAnnotation((eventAnn as any)?.object ?? eventAnn ?? api.getSelectedAnnotation?.()) ||
|
||||
currentTool;
|
||||
const stayOnPlacement =
|
||||
shouldStayOnPlacementTool(eventAnn, tool) ||
|
||||
(tool ? DRAWING_TOOL_IDS.includes(tool as any) : false);
|
||||
const stayOnPlacement = shouldStayOnPlacementTool(eventAnn, tool);
|
||||
if (activeToolRef.current !== 'select' && !stayOnPlacement) {
|
||||
activeToolRef.current = 'select';
|
||||
setActiveTool('select');
|
||||
@@ -318,9 +322,7 @@ export function useAnnotationSelection({
|
||||
applySelectionFromAnnotation(selected ?? eventAnn ?? null);
|
||||
const derivedAfter =
|
||||
deriveToolFromAnnotation((selected as any)?.object ?? selected ?? eventAnn ?? null) || activeToolRef.current;
|
||||
const stayOnPlacementAfter =
|
||||
shouldStayOnPlacementTool(selected ?? eventAnn ?? null, derivedAfter) ||
|
||||
(derivedAfter ? DRAWING_TOOL_IDS.includes(derivedAfter as any) : false);
|
||||
const stayOnPlacementAfter = shouldStayOnPlacementTool(selected ?? eventAnn ?? null, derivedAfter);
|
||||
if (activeToolRef.current !== 'select' && !stayOnPlacementAfter) {
|
||||
activeToolRef.current = 'select';
|
||||
setActiveTool('select');
|
||||
|
||||
Reference in New Issue
Block a user