mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
better
This commit is contained in:
parent
9f54df290d
commit
346a05748e
@ -19,9 +19,10 @@ public class ReactRoutingController {
|
||||
@Value("${server.servlet.context-path:/}")
|
||||
private String contextPath;
|
||||
|
||||
@GetMapping(value = {"/", "/index.html"}, produces = MediaType.TEXT_HTML_VALUE)
|
||||
public ResponseEntity<String> serveIndexHtml(HttpServletRequest request)
|
||||
throws IOException {
|
||||
@GetMapping(
|
||||
value = {"/", "/index.html"},
|
||||
produces = MediaType.TEXT_HTML_VALUE)
|
||||
public ResponseEntity<String> serveIndexHtml(HttpServletRequest request) throws IOException {
|
||||
ClassPathResource resource = new ClassPathResource("static/index.html");
|
||||
|
||||
try (InputStream inputStream = resource.getInputStream()) {
|
||||
@ -47,8 +48,7 @@ public class ReactRoutingController {
|
||||
|
||||
@GetMapping(
|
||||
"/{path:^(?!api|static|robots\\.txt|favicon\\.ico|manifest.*\\.json|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*$}")
|
||||
public ResponseEntity<String> forwardRootPaths(HttpServletRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<String> forwardRootPaths(HttpServletRequest request) throws IOException {
|
||||
return serveIndexHtml(request);
|
||||
}
|
||||
|
||||
|
||||
@ -331,7 +331,9 @@ public class SecurityConfiguration {
|
||||
formLogin ->
|
||||
formLogin
|
||||
.loginPage("/login") // Redirect here when unauthenticated
|
||||
.loginProcessingUrl("/perform_login") // Process form posts here (not /login)
|
||||
.loginProcessingUrl(
|
||||
"/perform_login") // Process form posts here (not
|
||||
// /login)
|
||||
.successHandler(
|
||||
new CustomAuthenticationSuccessHandler(
|
||||
loginAttemptService,
|
||||
|
||||
@ -3911,6 +3911,7 @@ saveChanges = "Save Changes"
|
||||
|
||||
[annotation]
|
||||
title = "Annotate"
|
||||
desc = "Use highlight, pen, text, and notes. Changes stay live—no flattening required."
|
||||
highlight = "Highlight"
|
||||
pen = "Pen"
|
||||
text = "Text box"
|
||||
@ -3921,8 +3922,39 @@ select = "Select"
|
||||
exit = "Exit annotation mode"
|
||||
strokeWidth = "Width"
|
||||
opacity = "Opacity"
|
||||
strokeOpacity = "Stroke Opacity"
|
||||
fillOpacity = "Fill Opacity"
|
||||
fontSize = "Font size"
|
||||
chooseColor = "Choose colour"
|
||||
color = "Colour"
|
||||
strokeColor = "Stroke Colour"
|
||||
fillColor = "Fill Colour"
|
||||
underline = "Underline"
|
||||
strikeout = "Strikeout"
|
||||
squiggly = "Squiggly"
|
||||
inkHighlighter = "Ink Highlighter"
|
||||
square = "Square"
|
||||
circle = "Circle"
|
||||
polygon = "Polygon"
|
||||
line = "Line"
|
||||
stamp = "Add Image"
|
||||
textMarkup = "Text Markup"
|
||||
drawing = "Drawing"
|
||||
shapes = "Shapes"
|
||||
notesStamps = "Notes & Stamps"
|
||||
settings = "Settings"
|
||||
borderOn = "Border: On"
|
||||
borderOff = "Border: Off"
|
||||
editInk = "Edit Pen"
|
||||
editLine = "Edit Line"
|
||||
editNote = "Edit Note"
|
||||
editText = "Edit Text Box"
|
||||
editTextMarkup = "Edit Text Markup"
|
||||
editSelected = "Edit Annotation"
|
||||
editSquare = "Edit Square"
|
||||
editCircle = "Edit Circle"
|
||||
editPolygon = "Edit Polygon"
|
||||
unsupportedType = "This annotation type is not fully supported for editing."
|
||||
|
||||
[search]
|
||||
title = "Search PDF"
|
||||
|
||||
@ -12,6 +12,7 @@ import { AppConfigProvider, AppConfigProviderProps, AppConfigRetryOptions } from
|
||||
import { RightRailProvider } from "@app/contexts/RightRailContext";
|
||||
import { ViewerProvider } from "@app/contexts/ViewerContext";
|
||||
import { SignatureProvider } from "@app/contexts/SignatureContext";
|
||||
import { AnnotationProvider } from "@app/contexts/AnnotationContext";
|
||||
import { TourOrchestrationProvider } from "@app/contexts/TourOrchestrationContext";
|
||||
import { AdminTourOrchestrationProvider } from "@app/contexts/AdminTourOrchestrationContext";
|
||||
import { PageEditorProvider } from "@app/contexts/PageEditorContext";
|
||||
@ -93,13 +94,15 @@ export function AppProviders({ children, appConfigRetryOptions, appConfigProvide
|
||||
<ViewerProvider>
|
||||
<PageEditorProvider>
|
||||
<SignatureProvider>
|
||||
<RightRailProvider>
|
||||
<TourOrchestrationProvider>
|
||||
<AdminTourOrchestrationProvider>
|
||||
{children}
|
||||
</AdminTourOrchestrationProvider>
|
||||
</TourOrchestrationProvider>
|
||||
</RightRailProvider>
|
||||
<AnnotationProvider>
|
||||
<RightRailProvider>
|
||||
<TourOrchestrationProvider>
|
||||
<AdminTourOrchestrationProvider>
|
||||
{children}
|
||||
</AdminTourOrchestrationProvider>
|
||||
</TourOrchestrationProvider>
|
||||
</RightRailProvider>
|
||||
</AnnotationProvider>
|
||||
</SignatureProvider>
|
||||
</PageEditorProvider>
|
||||
</ViewerProvider>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Modal, Stack, ColorPicker as MantineColorPicker, Group, Button, ColorSwatch } from '@mantine/core';
|
||||
import { Modal, Stack, ColorPicker as MantineColorPicker, Group, Button, ColorSwatch, Slider, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface ColorPickerProps {
|
||||
@ -8,6 +8,10 @@ interface ColorPickerProps {
|
||||
selectedColor: string;
|
||||
onColorChange: (color: string) => void;
|
||||
title?: string;
|
||||
opacity?: number;
|
||||
onOpacityChange?: (opacity: number) => void;
|
||||
showOpacity?: boolean;
|
||||
opacityLabel?: string;
|
||||
}
|
||||
|
||||
export const ColorPicker: React.FC<ColorPickerProps> = ({
|
||||
@ -15,10 +19,15 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
|
||||
onClose,
|
||||
selectedColor,
|
||||
onColorChange,
|
||||
title
|
||||
title,
|
||||
opacity,
|
||||
onOpacityChange,
|
||||
showOpacity = false,
|
||||
opacityLabel,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const resolvedTitle = title ?? t('colorPicker.title', 'Choose colour');
|
||||
const resolvedOpacityLabel = opacityLabel ?? t('annotation.opacity', 'Opacity');
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -38,6 +47,23 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
|
||||
size="lg"
|
||||
fullWidth
|
||||
/>
|
||||
{showOpacity && onOpacityChange && opacity !== undefined && (
|
||||
<Stack gap="xs">
|
||||
<Text size="sm" fw={500}>{resolvedOpacityLabel}</Text>
|
||||
<Slider
|
||||
min={10}
|
||||
max={100}
|
||||
value={opacity}
|
||||
onChange={onOpacityChange}
|
||||
marks={[
|
||||
{ value: 25, label: '25%' },
|
||||
{ value: 50, label: '50%' },
|
||||
{ value: 75, label: '75%' },
|
||||
{ value: 100, label: '100%' },
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button onClick={onClose}>
|
||||
{t('common.done', 'Done')}
|
||||
|
||||
196
frontend/src/core/components/viewer/AnnotationAPIBridge.tsx
Normal file
196
frontend/src/core/components/viewer/AnnotationAPIBridge.tsx
Normal file
@ -0,0 +1,196 @@
|
||||
import { useImperativeHandle, forwardRef, useCallback } from 'react';
|
||||
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
||||
import { PdfAnnotationSubtype } 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 buildAnnotationDefaults = useCallback(
|
||||
(toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
|
||||
switch (toolId) {
|
||||
case 'highlight':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.HIGHLIGHT,
|
||||
color: options?.color ?? '#ffd54f',
|
||||
opacity: options?.opacity ?? 0.6,
|
||||
};
|
||||
case 'underline':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.UNDERLINE,
|
||||
color: options?.color ?? '#ffb300',
|
||||
opacity: options?.opacity ?? 1,
|
||||
};
|
||||
case 'strikeout':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.STRIKEOUT,
|
||||
color: options?.color ?? '#e53935',
|
||||
opacity: options?.opacity ?? 1,
|
||||
};
|
||||
case 'squiggly':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.SQUIGGLY,
|
||||
color: options?.color ?? '#00acc1',
|
||||
opacity: options?.opacity ?? 1,
|
||||
};
|
||||
case 'ink':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.INK,
|
||||
color: options?.color ?? '#1f2933',
|
||||
borderWidth: options?.thickness ?? 2,
|
||||
strokeWidth: options?.thickness ?? 2,
|
||||
lineWidth: options?.thickness ?? 2,
|
||||
};
|
||||
case 'inkHighlighter':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.INK,
|
||||
color: options?.color ?? '#ffd54f',
|
||||
opacity: options?.opacity ?? 0.6,
|
||||
borderWidth: options?.thickness ?? 6,
|
||||
strokeWidth: options?.thickness ?? 6,
|
||||
lineWidth: options?.thickness ?? 6,
|
||||
};
|
||||
case 'text':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.FREETEXT,
|
||||
textColor: options?.color ?? '#111111',
|
||||
fontSize: options?.fontSize ?? 14,
|
||||
fontFamily: options?.fontFamily ?? 'Helvetica',
|
||||
opacity: options?.opacity ?? 1,
|
||||
interiorColor: options?.fillColor ?? '#fffef7',
|
||||
borderWidth: options?.thickness ?? 1,
|
||||
};
|
||||
case 'note':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.FREETEXT,
|
||||
textColor: options?.color ?? '#1b1b1b',
|
||||
color: options?.color ?? '#ffa000',
|
||||
interiorColor: options?.fillColor ?? '#fff8e1',
|
||||
opacity: options?.opacity ?? 1,
|
||||
fontSize: options?.fontSize ?? 12,
|
||||
contents: 'Note',
|
||||
};
|
||||
case 'square':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.SQUARE,
|
||||
color: options?.color ?? '#0000ff',
|
||||
strokeColor: options?.strokeColor ?? '#cf5b5b',
|
||||
opacity: options?.opacity ?? 0.5,
|
||||
fillOpacity: options?.fillOpacity ?? 0.5,
|
||||
strokeOpacity: options?.strokeOpacity ?? 0.5,
|
||||
borderWidth: options?.borderWidth ?? 1,
|
||||
strokeWidth: options?.borderWidth ?? 1,
|
||||
lineWidth: options?.borderWidth ?? 1,
|
||||
};
|
||||
case 'circle':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.CIRCLE,
|
||||
color: options?.color ?? '#0000ff',
|
||||
strokeColor: options?.strokeColor ?? '#cf5b5b',
|
||||
opacity: options?.opacity ?? 0.5,
|
||||
fillOpacity: options?.fillOpacity ?? 0.5,
|
||||
strokeOpacity: options?.strokeOpacity ?? 0.5,
|
||||
borderWidth: options?.borderWidth ?? 1,
|
||||
strokeWidth: options?.borderWidth ?? 1,
|
||||
lineWidth: options?.borderWidth ?? 1,
|
||||
};
|
||||
case 'line':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.LINE,
|
||||
color: options?.color ?? '#1565c0',
|
||||
strokeColor: options?.color ?? '#1565c0',
|
||||
opacity: options?.opacity ?? 1,
|
||||
borderWidth: options?.borderWidth ?? 2,
|
||||
strokeWidth: options?.borderWidth ?? 2,
|
||||
lineWidth: options?.borderWidth ?? 2,
|
||||
};
|
||||
case 'lineArrow':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.LINE,
|
||||
color: options?.color ?? '#1565c0',
|
||||
strokeColor: options?.color ?? '#1565c0',
|
||||
opacity: options?.opacity ?? 1,
|
||||
borderWidth: options?.borderWidth ?? 2,
|
||||
startStyle: 'None',
|
||||
endStyle: 'ClosedArrow',
|
||||
lineEndingStyles: { start: 'None', end: 'ClosedArrow' },
|
||||
};
|
||||
case 'polyline':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.POLYLINE,
|
||||
color: options?.color ?? '#1565c0',
|
||||
opacity: options?.opacity ?? 1,
|
||||
borderWidth: options?.borderWidth ?? 2,
|
||||
};
|
||||
case 'polygon':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.POLYGON,
|
||||
color: options?.color ?? '#0000ff',
|
||||
strokeColor: options?.strokeColor ?? '#cf5b5b',
|
||||
opacity: options?.opacity ?? 0.5,
|
||||
fillOpacity: options?.fillOpacity ?? 0.5,
|
||||
strokeOpacity: options?.strokeOpacity ?? 0.5,
|
||||
borderWidth: options?.borderWidth ?? 1,
|
||||
strokeWidth: options?.borderWidth ?? 1,
|
||||
lineWidth: options?.borderWidth ?? 1,
|
||||
};
|
||||
case 'stamp':
|
||||
return {
|
||||
type: PdfAnnotationSubtype.STAMP,
|
||||
};
|
||||
case 'select':
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const configureAnnotationTool = useCallback(
|
||||
(toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
|
||||
if (!annotationApi) return;
|
||||
|
||||
const defaults = buildAnnotationDefaults(toolId, options);
|
||||
const api = annotationApi as any;
|
||||
|
||||
if (defaults) {
|
||||
api.setToolDefaults?.(toolId, defaults);
|
||||
}
|
||||
|
||||
api.setActiveTool?.(toolId === 'select' ? null : toolId);
|
||||
},
|
||||
[annotationApi, buildAnnotationDefaults]
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
activateAnnotationTool: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
|
||||
configureAnnotationTool(toolId, options);
|
||||
},
|
||||
setAnnotationStyle: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
|
||||
const defaults = buildAnnotationDefaults(toolId, options);
|
||||
const api = annotationApi as any;
|
||||
if (defaults && api?.setToolDefaults) {
|
||||
api.setToolDefaults(toolId, defaults);
|
||||
}
|
||||
},
|
||||
getSelectedAnnotation: () => {
|
||||
const api = annotationApi as any;
|
||||
return api?.getSelectedAnnotation?.() ?? null;
|
||||
},
|
||||
deselectAnnotation: () => {
|
||||
const api = annotationApi as any;
|
||||
api?.deselectAnnotation?.();
|
||||
},
|
||||
updateAnnotation: (pageIndex: number, annotationId: string, patch: Partial<any>) => {
|
||||
const api = annotationApi as any;
|
||||
api?.updateAnnotation?.(pageIndex, annotationId, patch);
|
||||
},
|
||||
deactivateTools: () => {
|
||||
if (!annotationApi) return;
|
||||
const api = annotationApi as any;
|
||||
api?.setActiveTool?.(null);
|
||||
},
|
||||
}), [annotationApi, configureAnnotationTool, buildAnnotationDefaults]);
|
||||
|
||||
return null;
|
||||
});
|
||||
@ -11,6 +11,7 @@ import { ThumbnailSidebar } from '@app/components/viewer/ThumbnailSidebar';
|
||||
import { BookmarkSidebar } from '@app/components/viewer/BookmarkSidebar';
|
||||
import { useNavigationGuard, useNavigationState } from '@app/contexts/NavigationContext';
|
||||
import { useSignature } from '@app/contexts/SignatureContext';
|
||||
import { useAnnotation } from '@app/contexts/AnnotationContext';
|
||||
import { createStirlingFilesAndStubs } from '@app/services/fileStubHelpers';
|
||||
import NavigationWarningModal from '@app/components/shared/NavigationWarningModal';
|
||||
import { isStirlingFile } from '@app/types/fileContext';
|
||||
@ -67,8 +68,9 @@ const EmbedPdfViewerContent = ({
|
||||
}
|
||||
}, [rotationState.rotation]);
|
||||
|
||||
// Get signature context
|
||||
// Get signature and annotation contexts
|
||||
const { signatureApiRef, historyApiRef, signatureConfig, isPlacementMode } = useSignature();
|
||||
const { annotationApiRef } = useAnnotation();
|
||||
|
||||
// Get current file from FileContext
|
||||
const { selectors, state } = useFileState();
|
||||
@ -324,6 +326,7 @@ const EmbedPdfViewerContent = ({
|
||||
url={effectiveFile.url}
|
||||
enableAnnotations={shouldEnableAnnotations}
|
||||
signatureApiRef={signatureApiRef as React.RefObject<any>}
|
||||
annotationApiRef={annotationApiRef as React.RefObject<any>}
|
||||
historyApiRef={historyApiRef as React.RefObject<any>}
|
||||
onSignatureAdded={() => {
|
||||
// Handle signature added - for debugging, enable console logs as needed
|
||||
|
||||
@ -38,8 +38,9 @@ import { SearchAPIBridge } from '@app/components/viewer/SearchAPIBridge';
|
||||
import { ThumbnailAPIBridge } from '@app/components/viewer/ThumbnailAPIBridge';
|
||||
import { RotateAPIBridge } from '@app/components/viewer/RotateAPIBridge';
|
||||
import { SignatureAPIBridge } from '@app/components/viewer/SignatureAPIBridge';
|
||||
import { AnnotationAPIBridge } from '@app/components/viewer/AnnotationAPIBridge';
|
||||
import { HistoryAPIBridge } from '@app/components/viewer/HistoryAPIBridge';
|
||||
import type { SignatureAPI, HistoryAPI } from '@app/components/viewer/viewerTypes';
|
||||
import type { SignatureAPI, AnnotationAPI, HistoryAPI } from '@app/components/viewer/viewerTypes';
|
||||
import { ExportAPIBridge } from '@app/components/viewer/ExportAPIBridge';
|
||||
import { BookmarkAPIBridge } from '@app/components/viewer/BookmarkAPIBridge';
|
||||
import { PrintAPIBridge } from '@app/components/viewer/PrintAPIBridge';
|
||||
@ -53,10 +54,11 @@ interface LocalEmbedPDFProps {
|
||||
enableAnnotations?: boolean;
|
||||
onSignatureAdded?: (annotation: any) => void;
|
||||
signatureApiRef?: React.RefObject<SignatureAPI>;
|
||||
annotationApiRef?: React.RefObject<AnnotationAPI>;
|
||||
historyApiRef?: React.RefObject<HistoryAPI>;
|
||||
}
|
||||
|
||||
export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatureAdded, signatureApiRef, historyApiRef }: LocalEmbedPDFProps) {
|
||||
export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatureAdded, signatureApiRef, annotationApiRef, historyApiRef }: LocalEmbedPDFProps) {
|
||||
const { t } = useTranslation();
|
||||
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
||||
const [, setAnnotations] = useState<Array<{id: string, pageIndex: number, rect: any}>>([]);
|
||||
@ -329,6 +331,8 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
color: '#1f2933',
|
||||
opacity: 1,
|
||||
borderWidth: 2,
|
||||
lineWidth: 2,
|
||||
strokeWidth: 2,
|
||||
},
|
||||
behavior: {
|
||||
deactivateToolAfterCreate: false,
|
||||
@ -346,6 +350,8 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
color: '#ffd54f',
|
||||
opacity: 0.5,
|
||||
borderWidth: 6,
|
||||
lineWidth: 6,
|
||||
strokeWidth: 6,
|
||||
},
|
||||
behavior: {
|
||||
deactivateToolAfterCreate: false,
|
||||
@ -360,10 +366,12 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.SQUARE ? 10 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.SQUARE,
|
||||
color: '#1565c0',
|
||||
interiorColor: '#e3f2fd',
|
||||
opacity: 0.35,
|
||||
borderWidth: 2,
|
||||
color: '#0000ff', // fill color (blue)
|
||||
strokeColor: '#cf5b5b', // border color (reddish pink)
|
||||
opacity: 0.5,
|
||||
borderWidth: 1,
|
||||
strokeWidth: 1,
|
||||
lineWidth: 1,
|
||||
},
|
||||
clickBehavior: {
|
||||
enabled: true,
|
||||
@ -382,10 +390,12 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.CIRCLE ? 10 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.CIRCLE,
|
||||
color: '#1565c0',
|
||||
interiorColor: '#e3f2fd',
|
||||
opacity: 0.35,
|
||||
borderWidth: 2,
|
||||
color: '#0000ff', // fill color (blue)
|
||||
strokeColor: '#cf5b5b', // border color (reddish pink)
|
||||
opacity: 0.5,
|
||||
borderWidth: 1,
|
||||
strokeWidth: 1,
|
||||
lineWidth: 1,
|
||||
},
|
||||
clickBehavior: {
|
||||
enabled: true,
|
||||
@ -407,6 +417,8 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
color: '#1565c0',
|
||||
opacity: 1,
|
||||
borderWidth: 2,
|
||||
strokeWidth: 2,
|
||||
lineWidth: 2,
|
||||
},
|
||||
clickBehavior: {
|
||||
enabled: true,
|
||||
@ -472,10 +484,10 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
matchScore: (annotation) => (annotation.type === PdfAnnotationSubtype.POLYGON ? 10 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.POLYGON,
|
||||
color: '#1565c0',
|
||||
interiorColor: '#e3f2fd',
|
||||
opacity: 0.35,
|
||||
borderWidth: 2,
|
||||
color: '#0000ff', // fill color (blue)
|
||||
strokeColor: '#cf5b5b', // border color (reddish pink)
|
||||
opacity: 0.5,
|
||||
borderWidth: 1,
|
||||
},
|
||||
clickBehavior: {
|
||||
enabled: true,
|
||||
@ -604,6 +616,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
<ThumbnailAPIBridge />
|
||||
<RotateAPIBridge />
|
||||
{enableAnnotations && <SignatureAPIBridge ref={signatureApiRef} />}
|
||||
{enableAnnotations && <AnnotationAPIBridge ref={annotationApiRef} />}
|
||||
{enableAnnotations && <HistoryAPIBridge ref={historyApiRef} />}
|
||||
<ExportAPIBridge />
|
||||
<BookmarkAPIBridge />
|
||||
|
||||
@ -533,6 +533,10 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI>(function SignatureAPI
|
||||
getSelectedAnnotation: () => {
|
||||
return annotationApi?.getSelectedAnnotation?.() ?? null;
|
||||
},
|
||||
deselectAnnotation: () => {
|
||||
const api = annotationApi as any;
|
||||
api?.deselectAnnotation?.();
|
||||
},
|
||||
updateAnnotation: (pageIndex: number, annotationId: string, patch: Partial<any>) => {
|
||||
annotationApi?.updateAnnotation?.(pageIndex, annotationId, patch);
|
||||
},
|
||||
|
||||
@ -17,9 +17,19 @@ export interface SignatureAPI {
|
||||
activateAnnotationTool?: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => void;
|
||||
setAnnotationStyle?: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => void;
|
||||
getSelectedAnnotation?: () => any | null;
|
||||
deselectAnnotation?: () => void;
|
||||
updateAnnotation?: (pageIndex: number, annotationId: string, patch: Partial<any>) => void;
|
||||
}
|
||||
|
||||
export interface AnnotationAPI {
|
||||
activateAnnotationTool: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => void;
|
||||
setAnnotationStyle: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => void;
|
||||
getSelectedAnnotation: () => any | null;
|
||||
deselectAnnotation: () => void;
|
||||
updateAnnotation: (pageIndex: number, annotationId: string, patch: Partial<any>) => void;
|
||||
deactivateTools: () => void;
|
||||
}
|
||||
|
||||
export interface HistoryAPI {
|
||||
undo: () => void;
|
||||
redo: () => void;
|
||||
@ -52,6 +62,8 @@ export interface AnnotationToolOptions {
|
||||
color?: string;
|
||||
fillColor?: string;
|
||||
opacity?: number;
|
||||
strokeOpacity?: number;
|
||||
fillOpacity?: number;
|
||||
thickness?: number;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
|
||||
26
frontend/src/core/contexts/AnnotationContext.tsx
Normal file
26
frontend/src/core/contexts/AnnotationContext.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { createContext, useContext, ReactNode, useRef } from 'react';
|
||||
import type { AnnotationAPI } from '@app/components/viewer/viewerTypes';
|
||||
|
||||
interface AnnotationContextValue {
|
||||
annotationApiRef: React.RefObject<AnnotationAPI | null>;
|
||||
}
|
||||
|
||||
const AnnotationContext = createContext<AnnotationContextValue | undefined>(undefined);
|
||||
|
||||
export const AnnotationProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const annotationApiRef = useRef<AnnotationAPI>(null);
|
||||
|
||||
const value: AnnotationContextValue = {
|
||||
annotationApiRef,
|
||||
};
|
||||
|
||||
return <AnnotationContext.Provider value={value}>{children}</AnnotationContext.Provider>;
|
||||
};
|
||||
|
||||
export const useAnnotation = (): AnnotationContextValue => {
|
||||
const context = useContext(AnnotationContext);
|
||||
if (!context) {
|
||||
throw new Error('useAnnotation must be used within an AnnotationProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user