This commit is contained in:
Reece Browne 2025-09-11 22:51:10 +01:00
parent fb9b01f53b
commit 8815575124
6 changed files with 155 additions and 28 deletions

View File

@ -14,6 +14,7 @@
"@embedpdf/engines": "^1.1.1",
"@embedpdf/plugin-interaction-manager": "^1.1.1",
"@embedpdf/plugin-loader": "^1.1.1",
"@embedpdf/plugin-pan": "^1.1.1",
"@embedpdf/plugin-render": "^1.1.1",
"@embedpdf/plugin-scroll": "^1.1.1",
"@embedpdf/plugin-selection": "^1.1.1",
@ -679,6 +680,23 @@
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-pan": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.1.1.tgz",
"integrity": "sha512-38ZUJ+AYGXaL1sMVRghbJWdqqcfu7uO216yU/kTcCYs4hKgReW6Nw4XGGKo2beWIWfSqcSiD/3G4Y3RSBNsaYg==",
"dependencies": {
"@embedpdf/models": "1.1.1"
},
"peerDependencies": {
"@embedpdf/core": "1.1.1",
"@embedpdf/plugin-interaction-manager": "1.1.1",
"@embedpdf/plugin-viewport": "1.1.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-render": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.1.1.tgz",

View File

@ -10,6 +10,7 @@
"@embedpdf/engines": "^1.1.1",
"@embedpdf/plugin-interaction-manager": "^1.1.1",
"@embedpdf/plugin-loader": "^1.1.1",
"@embedpdf/plugin-pan": "^1.1.1",
"@embedpdf/plugin-render": "^1.1.1",
"@embedpdf/plugin-scroll": "^1.1.1",
"@embedpdf/plugin-selection": "^1.1.1",

View File

@ -7,6 +7,7 @@ import { useRightRail } from '../../contexts/RightRailContext';
import { useFileState, useFileSelection, useFileManagement } from '../../contexts/FileContext';
import { useNavigationState } from '../../contexts/NavigationContext';
import { useTranslation } from 'react-i18next';
import { usePanState } from '../../hooks/usePanState';
import LanguageSelector from '../shared/LanguageSelector';
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
@ -15,6 +16,7 @@ import BulkSelectionPanel from '../pageEditor/BulkSelectionPanel';
export default function RightRail() {
const { t } = useTranslation();
const isPanning = usePanState();
const { toggleTheme } = useRainbowThemeContext();
const { buttons, actions } = useRightRail();
const topButtons = useMemo(() => buttons.filter(b => (b.section || 'top') === 'top' && (b.visible ?? true)), [buttons]);
@ -265,10 +267,11 @@ export default function RightRail() {
{/* Pan Mode */}
<Tooltip content={t('rightRail.panMode', 'Pan Mode')} position="left" offset={12} arrow>
<ActionIcon
variant="subtle"
variant={isPanning ? "filled" : "subtle"}
color={isPanning ? "blue" : undefined}
radius="md"
className="right-rail-icon"
onClick={() => (window as any).embedPdfControls?.pan()}
onClick={() => (window as any).embedPdfPan?.togglePan()}
disabled={currentView !== 'viewer'}
>
<LocalIcon icon="pan-tool-rounded" width="1.5rem" height="1.5rem" />

View File

@ -9,12 +9,14 @@ import { Scroller, ScrollPluginPackage, ScrollStrategy } from '@embedpdf/plugin-
import { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/react';
import { ZoomPluginPackage, ZoomMode } from '@embedpdf/plugin-zoom/react';
import { InteractionManagerPluginPackage, PagePointerProvider } from '@embedpdf/plugin-interaction-manager/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 { ZoomControlsExporter } from './ZoomControlsExporter';
import { ScrollControlsExporter } from './ScrollControlsExporter';
import { SelectionControlsExporter } from './SelectionControlsExporter';
import { PanControlsExporter } from './PanControlsExporter';
interface LocalEmbedPDFProps {
file?: File | Blob;
@ -68,6 +70,11 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
// Register selection plugin (depends on InteractionManager)
createPluginRegistration(SelectionPluginPackage),
// Register pan plugin (depends on Viewport, InteractionManager)
createPluginRegistration(PanPluginPackage, {
defaultMode: 'mobile', // Try mobile mode which might be more permissive
}),
// Register zoom plugin with configuration
createPluginRegistration(ZoomPluginPackage, {
defaultZoomLevel: ZoomMode.FitPage,
@ -158,6 +165,8 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
<ZoomControlsExporter />
<ScrollControlsExporter />
<SelectionControlsExporter />
<PanControlsExporter />
<GlobalPointerProvider>
<Viewport
style={{
backgroundColor: actualColorScheme === 'dark' ? '#1a1b1e' : '#f1f3f5',
@ -175,7 +184,22 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
>
<Scroller
renderPage={({ width, height, pageIndex, scale, rotation }: { width: number; height: number; pageIndex: number; scale: number; rotation?: number }) => (
<PagePointerProvider {...{ width, height, pageIndex, scale, rotation: rotation || 0 }}>
<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()}
>
{/* 1. Low-resolution base layer for immediate feedback */}
<RenderLayer pageIndex={pageIndex} scale={0.5} />
@ -184,10 +208,12 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
{/* 3. Selection layer for text interaction */}
<SelectionLayer pageIndex={pageIndex} scale={scale} />
</div>
</PagePointerProvider>
)}
/>
</Viewport>
</GlobalPointerProvider>
</EmbedPDF>
</div>
);

View File

@ -0,0 +1,55 @@
import { useEffect, useState } from 'react';
import { usePan } from '@embedpdf/plugin-pan/react';
/**
* Component that runs inside EmbedPDF context and exports pan controls globally
*/
export function PanControlsExporter() {
const { provides: pan, isPanning } = usePan();
const [panStateListeners, setPanStateListeners] = useState<Array<(isPanning: boolean) => void>>([]);
useEffect(() => {
if (pan) {
// Export pan controls to global window for right rail access
(window as any).embedPdfPan = {
enablePan: () => {
console.log('EmbedPDF: Enabling pan mode');
pan.enablePan();
},
disablePan: () => {
console.log('EmbedPDF: Disabling pan mode');
pan.disablePan();
},
togglePan: () => {
console.log('EmbedPDF: Toggling pan mode, current isPanning:', isPanning);
pan.togglePan();
},
makePanDefault: () => pan.makePanDefault(),
isPanning: isPanning,
// Subscribe to pan state changes for reactive UI
onPanStateChange: (callback: (isPanning: boolean) => void) => {
setPanStateListeners(prev => [...prev, callback]);
// Return unsubscribe function
return () => {
setPanStateListeners(prev => prev.filter(cb => cb !== callback));
};
},
};
console.log('EmbedPDF pan controls exported to window.embedPdfPan', {
isPanning,
panAPI: pan,
availableMethods: Object.keys(pan)
});
} else {
console.warn('EmbedPDF pan API not available yet');
}
}, [pan, isPanning]);
// Notify all listeners when isPanning state changes
useEffect(() => {
panStateListeners.forEach(callback => callback(isPanning));
}, [isPanning, panStateListeners]);
return null; // This component doesn't render anything
}

View File

@ -0,0 +1,24 @@
import { useState, useEffect } from 'react';
/**
* Hook to track EmbedPDF pan state for reactive UI components
*/
export function usePanState() {
const [isPanning, setIsPanning] = useState(false);
useEffect(() => {
// Subscribe to pan state changes
const unsubscribe = (window as any).embedPdfPan?.onPanStateChange?.((newIsPanning: boolean) => {
setIsPanning(newIsPanning);
});
// Get initial state
if ((window as any).embedPdfPan?.isPanning !== undefined) {
setIsPanning((window as any).embedPdfPan.isPanning);
}
return unsubscribe || (() => {});
}, []);
return isPanning;
}