mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
improvements
This commit is contained in:
parent
346a05748e
commit
a6227160e2
@ -3932,7 +3932,7 @@ fillColor = "Fill Colour"
|
||||
underline = "Underline"
|
||||
strikeout = "Strikeout"
|
||||
squiggly = "Squiggly"
|
||||
inkHighlighter = "Ink Highlighter"
|
||||
inkHighlighter = "Freehand Highlighter"
|
||||
square = "Square"
|
||||
circle = "Circle"
|
||||
polygon = "Polygon"
|
||||
@ -3955,6 +3955,10 @@ editSquare = "Edit Square"
|
||||
editCircle = "Edit Circle"
|
||||
editPolygon = "Edit Polygon"
|
||||
unsupportedType = "This annotation type is not fully supported for editing."
|
||||
textAlignment = "Text Alignment"
|
||||
noteIcon = "Note Icon"
|
||||
imagePreview = "Preview"
|
||||
contents = "Text"
|
||||
|
||||
[search]
|
||||
title = "Search PDF"
|
||||
|
||||
@ -1,11 +1,24 @@
|
||||
import { useImperativeHandle, forwardRef, useCallback } from 'react';
|
||||
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
||||
import { PdfAnnotationSubtype } from '@embedpdf/models';
|
||||
import { PdfAnnotationSubtype, PdfAnnotationIcon } from '@embedpdf/models';
|
||||
import type { AnnotationToolId, AnnotationToolOptions, AnnotationAPI } from '@app/components/viewer/viewerTypes';
|
||||
|
||||
export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function AnnotationAPIBridge(_props, ref) {
|
||||
const annotationApi = useAnnotationCapability();
|
||||
|
||||
const getIconEnum = (icon?: string): PdfAnnotationIcon => {
|
||||
switch (icon) {
|
||||
case 'Comment': return PdfAnnotationIcon.Comment;
|
||||
case 'Key': return PdfAnnotationIcon.Key;
|
||||
case 'Note': return PdfAnnotationIcon.Note;
|
||||
case 'Help': return PdfAnnotationIcon.Help;
|
||||
case 'NewParagraph': return PdfAnnotationIcon.NewParagraph;
|
||||
case 'Paragraph': return PdfAnnotationIcon.Paragraph;
|
||||
case 'Insert': return PdfAnnotationIcon.Insert;
|
||||
default: return PdfAnnotationIcon.Comment;
|
||||
}
|
||||
};
|
||||
|
||||
const buildAnnotationDefaults = useCallback(
|
||||
(toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
|
||||
switch (toolId) {
|
||||
@ -62,13 +75,11 @@ export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function Annotation
|
||||
};
|
||||
case 'note':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.FREETEXT,
|
||||
textColor: options?.color ?? '#1b1b1b',
|
||||
type: PdfAnnotationSubtype.TEXT,
|
||||
color: options?.color ?? '#ffa000',
|
||||
interiorColor: options?.fillColor ?? '#fff8e1',
|
||||
opacity: options?.opacity ?? 1,
|
||||
fontSize: options?.fontSize ?? 12,
|
||||
contents: 'Note',
|
||||
icon: getIconEnum(options?.icon),
|
||||
contents: options?.contents ?? '',
|
||||
};
|
||||
case 'square':
|
||||
return {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useImperativeHandle, forwardRef, useEffect, useCallback, useRef, useState } from 'react';
|
||||
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
||||
import { PdfAnnotationSubtype, uuidV4 } from '@embedpdf/models';
|
||||
import { PdfAnnotationSubtype, PdfAnnotationIcon, uuidV4 } from '@embedpdf/models';
|
||||
import { useSignature } from '@app/contexts/SignatureContext';
|
||||
import type { AnnotationToolId, AnnotationToolOptions, SignatureAPI } from '@app/components/viewer/viewerTypes';
|
||||
import type { SignParameters } from '@app/hooks/tools/sign/useSignParameters';
|
||||
@ -199,6 +199,19 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI>(function SignatureAPI
|
||||
}
|
||||
}, [annotationApi, signatureConfig, placementPreviewSize, applyStampDefaults, cssToPdfSize]);
|
||||
|
||||
const getIconEnum = (icon?: string): PdfAnnotationIcon => {
|
||||
switch (icon) {
|
||||
case 'Comment': return PdfAnnotationIcon.Comment;
|
||||
case 'Key': return PdfAnnotationIcon.Key;
|
||||
case 'Note': return PdfAnnotationIcon.Note;
|
||||
case 'Help': return PdfAnnotationIcon.Help;
|
||||
case 'NewParagraph': return PdfAnnotationIcon.NewParagraph;
|
||||
case 'Paragraph': return PdfAnnotationIcon.Paragraph;
|
||||
case 'Insert': return PdfAnnotationIcon.Insert;
|
||||
default: return PdfAnnotationIcon.Comment;
|
||||
}
|
||||
};
|
||||
|
||||
const buildAnnotationDefaults = useCallback(
|
||||
(toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
|
||||
switch (toolId) {
|
||||
@ -256,13 +269,11 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI>(function SignatureAPI
|
||||
};
|
||||
case 'note':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.FREETEXT,
|
||||
textColor: options?.color ?? '#1b1b1b',
|
||||
type: PdfAnnotationSubtype.TEXT,
|
||||
color: options?.color ?? '#ffa000',
|
||||
interiorColor: options?.fillColor ?? '#fff8e1',
|
||||
opacity: options?.opacity ?? 1,
|
||||
fontSize: options?.fontSize ?? 12,
|
||||
contents: 'Note',
|
||||
icon: getIconEnum(options?.icon),
|
||||
contents: options?.contents ?? '',
|
||||
};
|
||||
case 'square':
|
||||
return {
|
||||
|
||||
@ -68,4 +68,6 @@ export interface AnnotationToolOptions {
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
imageSrc?: string;
|
||||
icon?: 'Comment' | 'Key' | 'Note' | 'Help' | 'NewParagraph' | 'Paragraph' | 'Insert';
|
||||
contents?: string;
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
const [squigglyOpacity, setSquigglyOpacity] = useState(100);
|
||||
const [textColor, setTextColor] = useState('#111111');
|
||||
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);
|
||||
@ -53,24 +54,30 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
const [historyAvailability, setHistoryAvailability] = useState({ canUndo: false, canRedo: false });
|
||||
const manualToolSwitch = useRef<boolean>(false);
|
||||
|
||||
const buildToolOptions = useCallback((toolId: AnnotationToolId) => {
|
||||
const buildToolOptions = useCallback((toolId: AnnotationToolId, includeMetadata: boolean = true) => {
|
||||
const metadata = includeMetadata ? {
|
||||
customData: {
|
||||
author: 'User', // Could be replaced with actual user name from auth context
|
||||
createdAt: new Date().toISOString(),
|
||||
modifiedAt: new Date().toISOString(),
|
||||
}
|
||||
} : {};
|
||||
|
||||
switch (toolId) {
|
||||
case 'ink':
|
||||
return { color: inkColor, thickness: inkWidth };
|
||||
return { color: inkColor, thickness: inkWidth, ...metadata };
|
||||
case 'inkHighlighter':
|
||||
return { color: highlightColor, opacity: highlightOpacity / 100, thickness: freehandHighlighterWidth };
|
||||
return { color: highlightColor, opacity: highlightOpacity / 100, thickness: freehandHighlighterWidth, ...metadata };
|
||||
case 'highlight':
|
||||
return { color: highlightColor, opacity: highlightOpacity / 100 };
|
||||
return { color: highlightColor, opacity: highlightOpacity / 100, ...metadata };
|
||||
case 'underline':
|
||||
return { color: underlineColor, opacity: underlineOpacity / 100 };
|
||||
return { color: underlineColor, opacity: underlineOpacity / 100, ...metadata };
|
||||
case 'strikeout':
|
||||
return { color: strikeoutColor, opacity: strikeoutOpacity / 100 };
|
||||
return { color: strikeoutColor, opacity: strikeoutOpacity / 100, ...metadata };
|
||||
case 'squiggly':
|
||||
return { color: squigglyColor, opacity: squigglyOpacity / 100 };
|
||||
return { color: squigglyColor, opacity: squigglyOpacity / 100, ...metadata };
|
||||
case 'text':
|
||||
return { color: textColor, fontSize: textSize };
|
||||
case 'note':
|
||||
return { color: textColor };
|
||||
return { color: textColor, fontSize: textSize, textAlign: textAlignment, ...metadata };
|
||||
case 'square':
|
||||
case 'circle':
|
||||
case 'polygon':
|
||||
@ -81,6 +88,7 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
strokeOpacity: shapeStrokeOpacity / 100,
|
||||
fillOpacity: shapeFillOpacity / 100,
|
||||
borderWidth: shapeThickness,
|
||||
...metadata,
|
||||
};
|
||||
case 'line':
|
||||
case 'polyline':
|
||||
@ -90,11 +98,12 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
strokeColor: shapeStrokeColor,
|
||||
opacity: shapeStrokeOpacity / 100,
|
||||
borderWidth: shapeThickness,
|
||||
...metadata,
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}, [highlightColor, highlightOpacity, inkColor, inkWidth, freehandHighlighterWidth, underlineColor, underlineOpacity, strikeoutColor, strikeoutOpacity, squigglyColor, squigglyOpacity, textColor, textSize, shapeStrokeColor, shapeFillColor, shapeStrokeOpacity, shapeFillOpacity, shapeThickness]);
|
||||
}, [highlightColor, highlightOpacity, inkColor, inkWidth, freehandHighlighterWidth, underlineColor, underlineOpacity, strikeoutColor, strikeoutOpacity, squigglyColor, squigglyOpacity, textColor, textSize, textAlignment, shapeStrokeColor, shapeFillColor, shapeOpacity, shapeStrokeOpacity, shapeFillOpacity, shapeThickness]);
|
||||
|
||||
useEffect(() => {
|
||||
setToolAndWorkbench('annotate', 'viewer');
|
||||
@ -216,7 +225,6 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
}
|
||||
} else {
|
||||
const typeToToolMap: Record<number, AnnotationToolId> = {
|
||||
1: 'note', // TEXT
|
||||
3: 'text', // FREETEXT
|
||||
4: 'line', // LINE
|
||||
5: 'square', // SQUARE
|
||||
@ -262,7 +270,6 @@ 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: 'stamp', label: t('annotation.stamp', 'Add Image'), icon: 'add-photo-alternate' },
|
||||
];
|
||||
|
||||
@ -433,10 +440,38 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
)}
|
||||
|
||||
{activeTool === 'text' && (
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.fontSize', 'Font size')}</Text>
|
||||
<Slider min={8} max={32} value={textSize} onChange={setTextSize} />
|
||||
</Box>
|
||||
<>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.fontSize', 'Font size')}</Text>
|
||||
<Slider min={8} max={32} value={textSize} onChange={setTextSize} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.textAlignment', 'Text Alignment')}</Text>
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant={textAlignment === 'left' ? 'filled' : 'default'}
|
||||
onClick={() => setTextAlignment('left')}
|
||||
size="md"
|
||||
>
|
||||
<LocalIcon icon="format-align-left" width={18} height={18} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={textAlignment === 'center' ? 'filled' : 'default'}
|
||||
onClick={() => setTextAlignment('center')}
|
||||
size="md"
|
||||
>
|
||||
<LocalIcon icon="format-align-center" width={18} height={18} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={textAlignment === 'right' ? 'filled' : 'default'}
|
||||
onClick={() => setTextAlignment('right')}
|
||||
size="md"
|
||||
>
|
||||
<LocalIcon icon="format-align-right" width={18} height={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{['square', 'circle', 'line', 'polygon'].includes(activeTool) && (
|
||||
@ -559,11 +594,11 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Type 3: Text box, Type 1: Note
|
||||
if ([1, 3].includes(type)) {
|
||||
// Type 3: Text box
|
||||
if (type === 3) {
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>{type === 3 ? t('annotation.editText', 'Edit Text Box') : t('annotation.editNote', 'Edit Note')}</Text>
|
||||
<Text size="sm" fw={600}>{t('annotation.editText', 'Edit Text Box')}</Text>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.color', 'Color')}</Text>
|
||||
<ColorSwatchButton
|
||||
@ -593,23 +628,21 @@ const Annotate = (_props: BaseToolProps) => {
|
||||
}, 120);
|
||||
}}
|
||||
/>
|
||||
{type === 3 && (
|
||||
<NumberInput
|
||||
label={t('annotation.fontSize', 'Font size')}
|
||||
min={6}
|
||||
max={72}
|
||||
value={selectedFontSize}
|
||||
onChange={(val) => {
|
||||
const size = typeof val === 'number' ? val : 14;
|
||||
setSelectedFontSize(size);
|
||||
signatureApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ fontSize: size }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<NumberInput
|
||||
label={t('annotation.fontSize', 'Font size')}
|
||||
min={6}
|
||||
max={72}
|
||||
value={selectedFontSize}
|
||||
onChange={(val) => {
|
||||
const size = typeof val === 'number' ? val : 14;
|
||||
setSelectedFontSize(size);
|
||||
signatureApiRef?.current?.updateAnnotation?.(
|
||||
selectedAnn.object?.pageIndex ?? 0,
|
||||
selectedAnn.object?.id,
|
||||
{ fontSize: size }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={4}>{t('annotation.opacity', 'Opacity')}</Text>
|
||||
<Slider
|
||||
|
||||
Loading…
Reference in New Issue
Block a user