mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
## Description of Changes - Removed unused `React` default imports across multiple frontend components. - Updated imports to only include required React hooks and types (e.g., `useState`, `useEffect`, `Suspense`, `createContext`). - Ensured consistency with React 17+ JSX transform, where default `React` import is no longer required. - This cleanup reduces bundle size slightly and aligns code with modern React best practices. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
316 lines
10 KiB
TypeScript
316 lines
10 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import { createPluginRegistration } from '@embedpdf/core';
|
|
import { EmbedPDF } from '@embedpdf/core/react';
|
|
import { usePdfiumEngine } from '@embedpdf/engines/react';
|
|
|
|
// Import the essential plugins
|
|
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
|
import { Scroller, ScrollPluginPackage, ScrollStrategy } from '@embedpdf/plugin-scroll/react';
|
|
import { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
|
|
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
|
import { ZoomPluginPackage } from '@embedpdf/plugin-zoom/react';
|
|
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
|
|
import { SelectionLayer, SelectionPluginPackage } from '@embedpdf/plugin-selection/react';
|
|
import { TilingLayer, TilingPluginPackage } from '@embedpdf/plugin-tiling/react';
|
|
import { PanPluginPackage } from '@embedpdf/plugin-pan/react';
|
|
import { SpreadPluginPackage, SpreadMode } from '@embedpdf/plugin-spread/react';
|
|
import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
|
|
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
|
|
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
|
|
import { Rotation } from '@embedpdf/models';
|
|
|
|
// Import annotation plugins
|
|
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
|
|
import { AnnotationLayer, AnnotationPluginPackage } from '@embedpdf/plugin-annotation/react';
|
|
import { PdfAnnotationSubtype } from '@embedpdf/models';
|
|
|
|
import { CustomSearchLayer } from './CustomSearchLayer';
|
|
import { ZoomAPIBridge } from './ZoomAPIBridge';
|
|
import ToolLoadingFallback from '../tools/ToolLoadingFallback';
|
|
import { Center, Stack, Text } from '@mantine/core';
|
|
import { ScrollAPIBridge } from './ScrollAPIBridge';
|
|
import { SelectionAPIBridge } from './SelectionAPIBridge';
|
|
import { PanAPIBridge } from './PanAPIBridge';
|
|
import { SpreadAPIBridge } from './SpreadAPIBridge';
|
|
import { SearchAPIBridge } from './SearchAPIBridge';
|
|
import { ThumbnailAPIBridge } from './ThumbnailAPIBridge';
|
|
import { RotateAPIBridge } from './RotateAPIBridge';
|
|
|
|
interface LocalEmbedPDFWithAnnotationsProps {
|
|
file?: File | Blob;
|
|
url?: string | null;
|
|
onAnnotationChange?: (annotations: any[]) => void;
|
|
}
|
|
|
|
export function LocalEmbedPDFWithAnnotations({
|
|
file,
|
|
url,
|
|
onAnnotationChange
|
|
}: LocalEmbedPDFWithAnnotationsProps) {
|
|
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
|
|
|
// Convert File to URL if needed
|
|
useEffect(() => {
|
|
if (file) {
|
|
const objectUrl = URL.createObjectURL(file);
|
|
setPdfUrl(objectUrl);
|
|
return () => URL.revokeObjectURL(objectUrl);
|
|
} else if (url) {
|
|
setPdfUrl(url);
|
|
}
|
|
}, [file, url]);
|
|
|
|
// Create plugins configuration with annotation support
|
|
const plugins = useMemo(() => {
|
|
if (!pdfUrl) return [];
|
|
|
|
return [
|
|
createPluginRegistration(LoaderPluginPackage, {
|
|
loadingOptions: {
|
|
type: 'url',
|
|
pdfFile: {
|
|
id: 'stirling-pdf-signing-viewer',
|
|
url: pdfUrl,
|
|
},
|
|
},
|
|
}),
|
|
createPluginRegistration(ViewportPluginPackage, {
|
|
viewportGap: 10,
|
|
}),
|
|
createPluginRegistration(ScrollPluginPackage, {
|
|
strategy: ScrollStrategy.Vertical,
|
|
initialPage: 0,
|
|
}),
|
|
createPluginRegistration(RenderPluginPackage),
|
|
|
|
// Register interaction manager (required for annotations)
|
|
createPluginRegistration(InteractionManagerPluginPackage),
|
|
|
|
// Register selection plugin (depends on InteractionManager)
|
|
createPluginRegistration(SelectionPluginPackage),
|
|
|
|
// Register history plugin for undo/redo (recommended for annotations)
|
|
createPluginRegistration(HistoryPluginPackage),
|
|
|
|
// Register annotation plugin (depends on InteractionManager, Selection, History)
|
|
createPluginRegistration(AnnotationPluginPackage, {
|
|
annotationAuthor: 'Digital Signature',
|
|
autoCommit: true,
|
|
deactivateToolAfterCreate: false,
|
|
selectAfterCreate: true,
|
|
}),
|
|
|
|
// Register pan plugin
|
|
createPluginRegistration(PanPluginPackage, {
|
|
defaultMode: 'mobile',
|
|
}),
|
|
|
|
// Register zoom plugin
|
|
createPluginRegistration(ZoomPluginPackage, {
|
|
defaultZoomLevel: 1.4,
|
|
minZoom: 0.2,
|
|
maxZoom: 3.0,
|
|
}),
|
|
|
|
// Register tiling plugin
|
|
createPluginRegistration(TilingPluginPackage, {
|
|
tileSize: 768,
|
|
overlapPx: 5,
|
|
extraRings: 1,
|
|
}),
|
|
|
|
// Register spread plugin
|
|
createPluginRegistration(SpreadPluginPackage, {
|
|
defaultSpreadMode: SpreadMode.None,
|
|
}),
|
|
|
|
// Register search plugin
|
|
createPluginRegistration(SearchPluginPackage),
|
|
|
|
// Register thumbnail plugin
|
|
createPluginRegistration(ThumbnailPluginPackage),
|
|
|
|
// Register rotate plugin
|
|
createPluginRegistration(RotatePluginPackage, {
|
|
defaultRotation: Rotation.Degree0,
|
|
}),
|
|
];
|
|
}, [pdfUrl]);
|
|
|
|
// Initialize the engine
|
|
const { engine, isLoading, error } = usePdfiumEngine();
|
|
|
|
// Early return if no file or URL provided
|
|
if (!file && !url) {
|
|
return (
|
|
<Center h="100%" w="100%">
|
|
<Stack align="center" gap="md">
|
|
<div style={{ fontSize: '24px' }}>📄</div>
|
|
<Text c="dimmed" size="sm">
|
|
No PDF provided
|
|
</Text>
|
|
</Stack>
|
|
</Center>
|
|
);
|
|
}
|
|
|
|
if (isLoading || !engine || !pdfUrl) {
|
|
return <ToolLoadingFallback toolName="PDF Engine" />;
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Center h="100%" w="100%">
|
|
<Stack align="center" gap="md">
|
|
<div style={{ fontSize: '24px' }}>❌</div>
|
|
<Text c="red" size="sm" style={{ textAlign: 'center' }}>
|
|
Error loading PDF engine: {error.message}
|
|
</Text>
|
|
</Stack>
|
|
</Center>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div style={{
|
|
height: '100%',
|
|
width: '100%',
|
|
position: 'relative',
|
|
overflow: 'hidden',
|
|
flex: 1,
|
|
minHeight: 0,
|
|
minWidth: 0
|
|
}}>
|
|
<EmbedPDF
|
|
engine={engine}
|
|
plugins={plugins}
|
|
onInitialized={async (registry) => {
|
|
const annotationPlugin = registry.getPlugin('annotation');
|
|
if (!annotationPlugin || !annotationPlugin.provides) return;
|
|
|
|
const annotationApi = annotationPlugin.provides();
|
|
if (!annotationApi) return;
|
|
|
|
// Add custom signature stamp tool
|
|
annotationApi.addTool({
|
|
id: 'signatureStamp',
|
|
name: 'Digital Signature',
|
|
interaction: { exclusive: false, cursor: 'copy' },
|
|
matchScore: () => 0,
|
|
defaults: {
|
|
type: PdfAnnotationSubtype.STAMP,
|
|
// Will be set dynamically when user creates signature
|
|
},
|
|
});
|
|
|
|
// Add custom ink signature tool
|
|
annotationApi.addTool({
|
|
id: 'signatureInk',
|
|
name: 'Signature Draw',
|
|
interaction: { exclusive: true, cursor: 'crosshair' },
|
|
matchScore: () => 0,
|
|
defaults: {
|
|
type: PdfAnnotationSubtype.INK,
|
|
color: '#000000',
|
|
opacity: 1.0,
|
|
borderWidth: 2,
|
|
},
|
|
});
|
|
|
|
// Listen for annotation events to notify parent
|
|
if (onAnnotationChange) {
|
|
annotationApi.onAnnotationEvent((event: any) => {
|
|
if (event.committed) {
|
|
// Get all annotations and notify parent
|
|
// This is a simplified approach - in reality you'd need to get all annotations
|
|
onAnnotationChange([event.annotation]);
|
|
}
|
|
});
|
|
}
|
|
}}
|
|
>
|
|
<ZoomAPIBridge />
|
|
<ScrollAPIBridge />
|
|
<SelectionAPIBridge />
|
|
<PanAPIBridge />
|
|
<SpreadAPIBridge />
|
|
<SearchAPIBridge />
|
|
<ThumbnailAPIBridge />
|
|
<RotateAPIBridge />
|
|
<GlobalPointerProvider>
|
|
<Viewport
|
|
style={{
|
|
backgroundColor: 'var(--bg-surface)',
|
|
height: '100%',
|
|
width: '100%',
|
|
maxHeight: '100%',
|
|
maxWidth: '100%',
|
|
overflow: 'auto',
|
|
position: 'relative',
|
|
flex: 1,
|
|
minHeight: 0,
|
|
minWidth: 0,
|
|
contain: 'strict',
|
|
}}
|
|
>
|
|
<Scroller
|
|
renderPage={({ width, height, pageIndex, scale, rotation }: {
|
|
width: number;
|
|
height: number;
|
|
pageIndex: number;
|
|
scale: number;
|
|
rotation?: number;
|
|
}) => (
|
|
<Rotate pageSize={{ width, height }}>
|
|
<PagePointerProvider {...{
|
|
pageWidth: width,
|
|
pageHeight: height,
|
|
pageIndex,
|
|
scale,
|
|
rotation: rotation || 0
|
|
}}>
|
|
<div
|
|
style={{
|
|
width,
|
|
height,
|
|
position: 'relative',
|
|
userSelect: 'none',
|
|
WebkitUserSelect: 'none',
|
|
MozUserSelect: 'none',
|
|
msUserSelect: 'none'
|
|
}}
|
|
draggable={false}
|
|
onDragStart={(e) => e.preventDefault()}
|
|
onDrop={(e) => e.preventDefault()}
|
|
onDragOver={(e) => e.preventDefault()}
|
|
>
|
|
{/* High-resolution tile layer */}
|
|
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
|
|
|
{/* Search highlight layer */}
|
|
<CustomSearchLayer pageIndex={pageIndex} scale={scale} />
|
|
|
|
{/* Selection layer for text interaction */}
|
|
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
|
|
|
{/* Annotation layer for signatures */}
|
|
<AnnotationLayer
|
|
pageIndex={pageIndex}
|
|
scale={scale}
|
|
pageWidth={width}
|
|
pageHeight={height}
|
|
rotation={rotation || 0}
|
|
selectionOutlineColor="#007ACC"
|
|
/>
|
|
</div>
|
|
</PagePointerProvider>
|
|
</Rotate>
|
|
)}
|
|
/>
|
|
</Viewport>
|
|
</GlobalPointerProvider>
|
|
</EmbedPDF>
|
|
</div>
|
|
);
|
|
}
|