mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-18 01:19:24 +00:00
pan
This commit is contained in:
parent
fb9b01f53b
commit
8815575124
18
frontend/package-lock.json
generated
18
frontend/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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" />
|
||||
|
@ -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,36 +165,55 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
|
||||
<ZoomControlsExporter />
|
||||
<ScrollControlsExporter />
|
||||
<SelectionControlsExporter />
|
||||
<Viewport
|
||||
style={{
|
||||
backgroundColor: actualColorScheme === 'dark' ? '#1a1b1e' : '#f1f3f5',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
maxHeight: '100%',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
contain: 'strict',
|
||||
}}
|
||||
>
|
||||
<PanControlsExporter />
|
||||
<GlobalPointerProvider>
|
||||
<Viewport
|
||||
style={{
|
||||
backgroundColor: actualColorScheme === 'dark' ? '#1a1b1e' : '#f1f3f5',
|
||||
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 }) => (
|
||||
<PagePointerProvider {...{ width, height, pageIndex, scale, rotation: rotation || 0 }}>
|
||||
{/* 1. Low-resolution base layer for immediate feedback */}
|
||||
<RenderLayer pageIndex={pageIndex} scale={0.5} />
|
||||
|
||||
{/* 2. High-resolution tile layer on top */}
|
||||
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
||||
|
||||
{/* 3. Selection layer for text interaction */}
|
||||
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
||||
<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} />
|
||||
|
||||
{/* 2. High-resolution tile layer on top */}
|
||||
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
||||
|
||||
{/* 3. Selection layer for text interaction */}
|
||||
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
||||
</div>
|
||||
</PagePointerProvider>
|
||||
)}
|
||||
/>
|
||||
</Viewport>
|
||||
</Viewport>
|
||||
</GlobalPointerProvider>
|
||||
</EmbedPDF>
|
||||
</div>
|
||||
);
|
||||
|
55
frontend/src/components/viewer/PanControlsExporter.tsx
Normal file
55
frontend/src/components/viewer/PanControlsExporter.tsx
Normal 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
|
||||
}
|
24
frontend/src/hooks/usePanState.ts
Normal file
24
frontend/src/hooks/usePanState.ts
Normal 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user