mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-01-14 20:11:17 +01:00
Annotation fixes
This commit is contained in:
parent
68f8bb749f
commit
72ddd997a2
@ -4,7 +4,8 @@ import { PdfAnnotationSubtype, PdfAnnotationIcon } from '@embedpdf/models';
|
|||||||
import type { AnnotationToolId, AnnotationToolOptions, AnnotationAPI } from '@app/components/viewer/viewerTypes';
|
import type { AnnotationToolId, AnnotationToolOptions, AnnotationAPI } from '@app/components/viewer/viewerTypes';
|
||||||
|
|
||||||
export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function AnnotationAPIBridge(_props, ref) {
|
export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function AnnotationAPIBridge(_props, ref) {
|
||||||
const annotationApi = useAnnotationCapability();
|
// Use the provided annotation API just like SignatureAPIBridge/HistoryAPIBridge
|
||||||
|
const { provides: annotationApi } = useAnnotationCapability();
|
||||||
|
|
||||||
const getIconEnum = (icon?: string): PdfAnnotationIcon => {
|
const getIconEnum = (icon?: string): PdfAnnotationIcon => {
|
||||||
switch (icon) {
|
switch (icon) {
|
||||||
@ -164,13 +165,16 @@ export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function Annotation
|
|||||||
if (!annotationApi) return;
|
if (!annotationApi) return;
|
||||||
|
|
||||||
const defaults = buildAnnotationDefaults(toolId, options);
|
const defaults = buildAnnotationDefaults(toolId, options);
|
||||||
const api = annotationApi as any;
|
|
||||||
|
|
||||||
if (defaults) {
|
// Reset tool first, then activate (like SignatureAPIBridge does)
|
||||||
api.setToolDefaults?.(toolId, defaults);
|
annotationApi.setActiveTool(null);
|
||||||
|
annotationApi.setActiveTool(toolId === 'select' ? null : toolId);
|
||||||
|
|
||||||
|
// Verify tool was activated before setting defaults (like SignatureAPIBridge does)
|
||||||
|
const activeTool = annotationApi.getActiveTool();
|
||||||
|
if (activeTool && activeTool.id === toolId && defaults) {
|
||||||
|
annotationApi.setToolDefaults(toolId, defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
api.setActiveTool?.(toolId === 'select' ? null : toolId);
|
|
||||||
},
|
},
|
||||||
[annotationApi, buildAnnotationDefaults]
|
[annotationApi, buildAnnotationDefaults]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -85,8 +85,8 @@ const EmbedPdfViewerContent = ({
|
|||||||
|
|
||||||
// Check if we're in an annotation tool
|
// Check if we're in an annotation tool
|
||||||
const { selectedTool } = useNavigationState();
|
const { selectedTool } = useNavigationState();
|
||||||
// Tools that require the annotation layer (Sign, Add Text, Add Image)
|
// Tools that require the annotation layer (Sign, Add Text, Add Image, Annotate)
|
||||||
const isInAnnotationTool = selectedTool === 'sign' || selectedTool === 'addText' || selectedTool === 'addImage';
|
const isInAnnotationTool = selectedTool === 'sign' || selectedTool === 'addText' || selectedTool === 'addImage' || selectedTool === 'annotate';
|
||||||
|
|
||||||
// Sync isAnnotationMode in ViewerContext with current tool
|
// Sync isAnnotationMode in ViewerContext with current tool
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -60,7 +60,7 @@ interface LocalEmbedPDFProps {
|
|||||||
historyApiRef?: React.RefObject<HistoryAPI>;
|
historyApiRef?: React.RefObject<HistoryAPI>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedAnnotations = true, onSignatureAdded, signatureApiRef, historyApiRef }: LocalEmbedPDFProps) {
|
export function LocalEmbedPDF({ file, url, enableAnnotations = false, showBakedAnnotations = true, onSignatureAdded, signatureApiRef, annotationApiRef, historyApiRef }: LocalEmbedPDFProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
||||||
const [, setAnnotations] = useState<Array<{id: string, pageIndex: number, rect: any}>>([]);
|
const [, setAnnotations] = useState<Array<{id: string, pageIndex: number, rect: any}>>([]);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useState, useContext, useCallback, useRef } from 'react';
|
import { useEffect, useMemo, useState, useContext, useCallback, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Alert, Text, Group, ActionIcon, Stack, Divider, Slider, Box, Tooltip as MantineTooltip, Button, TextInput, Textarea, NumberInput, Tooltip } from '@mantine/core';
|
import { Alert, Text, Group, ActionIcon, Stack, Divider, Slider, Box, Tooltip as MantineTooltip, Button, TextInput, Textarea, NumberInput, Tooltip, Paper } from '@mantine/core';
|
||||||
|
|
||||||
import { createToolFlow } from '@app/components/tools/shared/createToolFlow';
|
import { createToolFlow } from '@app/components/tools/shared/createToolFlow';
|
||||||
import { useNavigation } from '@app/contexts/NavigationContext';
|
import { useNavigation } from '@app/contexts/NavigationContext';
|
||||||
@ -12,6 +12,7 @@ import { ColorPicker, ColorSwatchButton } from '@app/components/annotation/share
|
|||||||
import { ImageUploader } from '@app/components/annotation/shared/ImageUploader';
|
import { ImageUploader } from '@app/components/annotation/shared/ImageUploader';
|
||||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||||
import type { AnnotationToolId } from '@app/components/viewer/viewerTypes';
|
import type { AnnotationToolId } from '@app/components/viewer/viewerTypes';
|
||||||
|
import { SuggestedToolsSection } from '@app/components/tools/shared/SuggestedToolsSection';
|
||||||
|
|
||||||
const Annotate = (_props: BaseToolProps) => {
|
const Annotate = (_props: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -366,10 +367,11 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const defaultStyleControls = (
|
const defaultStyleControls = (
|
||||||
<Stack gap="sm">
|
<Paper withBorder p="sm" radius="md">
|
||||||
{activeTool === 'stamp' ? (
|
<Stack gap="sm">
|
||||||
<>
|
{activeTool === 'stamp' ? (
|
||||||
<Text size="sm" fw={600}>{t('annotation.stampSettings', 'Stamp Settings')}</Text>
|
<>
|
||||||
|
<Text size="sm" fw={600}>{t('annotation.stampSettings', 'Stamp Settings')}</Text>
|
||||||
<ImageUploader
|
<ImageUploader
|
||||||
onImageChange={async (file) => {
|
onImageChange={async (file) => {
|
||||||
if (file) {
|
if (file) {
|
||||||
@ -575,9 +577,8 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</Stack>
|
||||||
)}
|
</Paper>
|
||||||
</Stack>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedAnnotationControls = selectedAnn && (() => {
|
const selectedAnnotationControls = selectedAnn && (() => {
|
||||||
@ -586,7 +587,8 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
// Type 9: Highlight, Type 10: Underline, Type 11: Squiggly, Type 12: Strikeout
|
// Type 9: Highlight, Type 10: Underline, Type 11: Squiggly, Type 12: Strikeout
|
||||||
if ([9, 10, 11, 12].includes(type)) {
|
if ([9, 10, 11, 12].includes(type)) {
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Paper withBorder p="sm" radius="md">
|
||||||
|
<Stack gap="sm">
|
||||||
<Text size="sm" fw={600}>{t('annotation.editTextMarkup', 'Edit Text Markup')}</Text>
|
<Text size="sm" fw={600}>{t('annotation.editTextMarkup', 'Edit Text Markup')}</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||||
@ -614,15 +616,17 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type 15: Ink (pen)
|
// Type 15: Ink (pen)
|
||||||
if (type === 15) {
|
if (type === 15) {
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Paper withBorder p="sm" radius="md">
|
||||||
<Text size="sm" fw={600}>{t('annotation.editInk', 'Edit Pen')}</Text>
|
<Stack gap="sm">
|
||||||
|
<Text size="sm" fw={600}>{t('annotation.editInk', 'Edit Pen')}</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||||
<ColorSwatchButton
|
<ColorSwatchButton
|
||||||
@ -654,15 +658,17 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type 3: Text box
|
// Type 3: Text box
|
||||||
if (type === 3) {
|
if (type === 3) {
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Paper withBorder p="sm" radius="md">
|
||||||
<Text size="sm" fw={600}>{t('annotation.editText', 'Edit Text Box')}</Text>
|
<Stack gap="sm">
|
||||||
|
<Text size="sm" fw={600}>{t('annotation.editText', 'Edit Text Box')}</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||||
<ColorSwatchButton
|
<ColorSwatchButton
|
||||||
@ -784,15 +790,17 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type 4: Line
|
// Type 4: Line
|
||||||
if (type === 4) {
|
if (type === 4) {
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Paper withBorder p="sm" radius="md">
|
||||||
<Text size="sm" fw={600}>{t('annotation.editLine', 'Edit Line')}</Text>
|
<Stack gap="sm">
|
||||||
|
<Text size="sm" fw={600}>{t('annotation.editLine', 'Edit Line')}</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||||
<ColorSwatchButton
|
<ColorSwatchButton
|
||||||
@ -839,7 +847,8 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -847,8 +856,9 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
if ([5, 6, 7].includes(type)) {
|
if ([5, 6, 7].includes(type)) {
|
||||||
const shapeName = type === 5 ? 'Square' : type === 6 ? 'Circle' : 'Polygon';
|
const shapeName = type === 5 ? 'Square' : type === 6 ? 'Circle' : 'Polygon';
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Paper withBorder p="sm" radius="md">
|
||||||
<Text size="sm" fw={600}>{t(`annotation.edit${shapeName}`, `Edit ${shapeName}`)}</Text>
|
<Stack gap="sm">
|
||||||
|
<Text size="sm" fw={600}>{t(`annotation.edit${shapeName}`, `Edit ${shapeName}`)}</Text>
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
<Stack gap={4} align="center">
|
<Stack gap={4} align="center">
|
||||||
<Text size="xs" c="dimmed">{t('annotation.strokeColor', 'Stroke Color')}</Text>
|
<Text size="xs" c="dimmed">{t('annotation.strokeColor', 'Stroke Color')}</Text>
|
||||||
@ -932,43 +942,24 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
}
|
}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default fallback
|
// Default fallback
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Paper withBorder p="sm" radius="md">
|
||||||
<Text size="sm" fw={600}>{t('annotation.editSelected', 'Edit Annotation')}</Text>
|
<Stack gap="sm">
|
||||||
<Text size="xs" c="dimmed">{t('annotation.unsupportedType', 'This annotation type is not fully supported for editing.')}</Text>
|
<Text size="sm" fw={600}>{t('annotation.editSelected', 'Edit Annotation')}</Text>
|
||||||
</Stack>
|
<Text size="xs" c="dimmed">{t('annotation.unsupportedType', 'This annotation type is not fully supported for editing.')}</Text>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const saveAndColorPicker = (
|
const colorPickerComponent = (
|
||||||
<>
|
<>
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
variant="light"
|
|
||||||
leftSection={<LocalIcon icon="save" width="1rem" height="1rem" />}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
const pdfArrayBuffer = await viewerContext?.exportActions?.saveAsCopy?.();
|
|
||||||
if (!pdfArrayBuffer) return;
|
|
||||||
const blob = new Blob([pdfArrayBuffer], { type: 'application/pdf' });
|
|
||||||
const fileName = selectors.getFiles()[0]?.name || 'annotated.pdf';
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
link.download = fileName;
|
|
||||||
link.click();
|
|
||||||
URL.revokeObjectURL(link.href);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to save annotated PDF', error);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('rightRail.save', 'Save')}
|
|
||||||
</Button>
|
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
isOpen={isColorPickerOpen}
|
isOpen={isColorPickerOpen}
|
||||||
onClose={() => setIsColorPickerOpen(false)}
|
onClose={() => setIsColorPickerOpen(false)}
|
||||||
@ -1224,8 +1215,11 @@ const Annotate = (_props: BaseToolProps) => {
|
|||||||
{/* Edit Selected */}
|
{/* Edit Selected */}
|
||||||
{selectedAnn && selectedAnnotationControls}
|
{selectedAnn && selectedAnnotationControls}
|
||||||
|
|
||||||
{/* Save Button */}
|
{/* Color Picker */}
|
||||||
{saveAndColorPicker}
|
{colorPickerComponent}
|
||||||
|
|
||||||
|
{/* Suggested Tools */}
|
||||||
|
<SuggestedToolsSection currentTool="annotate" />
|
||||||
</Stack>
|
</Stack>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user