mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-18 09:29: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/engines": "^1.1.1",
|
||||||
"@embedpdf/plugin-interaction-manager": "^1.1.1",
|
"@embedpdf/plugin-interaction-manager": "^1.1.1",
|
||||||
"@embedpdf/plugin-loader": "^1.1.1",
|
"@embedpdf/plugin-loader": "^1.1.1",
|
||||||
|
"@embedpdf/plugin-pan": "^1.1.1",
|
||||||
"@embedpdf/plugin-render": "^1.1.1",
|
"@embedpdf/plugin-render": "^1.1.1",
|
||||||
"@embedpdf/plugin-scroll": "^1.1.1",
|
"@embedpdf/plugin-scroll": "^1.1.1",
|
||||||
"@embedpdf/plugin-selection": "^1.1.1",
|
"@embedpdf/plugin-selection": "^1.1.1",
|
||||||
@ -679,6 +680,23 @@
|
|||||||
"vue": ">=3.2.0"
|
"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": {
|
"node_modules/@embedpdf/plugin-render": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.1.1.tgz",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"@embedpdf/engines": "^1.1.1",
|
"@embedpdf/engines": "^1.1.1",
|
||||||
"@embedpdf/plugin-interaction-manager": "^1.1.1",
|
"@embedpdf/plugin-interaction-manager": "^1.1.1",
|
||||||
"@embedpdf/plugin-loader": "^1.1.1",
|
"@embedpdf/plugin-loader": "^1.1.1",
|
||||||
|
"@embedpdf/plugin-pan": "^1.1.1",
|
||||||
"@embedpdf/plugin-render": "^1.1.1",
|
"@embedpdf/plugin-render": "^1.1.1",
|
||||||
"@embedpdf/plugin-scroll": "^1.1.1",
|
"@embedpdf/plugin-scroll": "^1.1.1",
|
||||||
"@embedpdf/plugin-selection": "^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 { useFileState, useFileSelection, useFileManagement } from '../../contexts/FileContext';
|
||||||
import { useNavigationState } from '../../contexts/NavigationContext';
|
import { useNavigationState } from '../../contexts/NavigationContext';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { usePanState } from '../../hooks/usePanState';
|
||||||
|
|
||||||
import LanguageSelector from '../shared/LanguageSelector';
|
import LanguageSelector from '../shared/LanguageSelector';
|
||||||
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
|
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
|
||||||
@ -15,6 +16,7 @@ import BulkSelectionPanel from '../pageEditor/BulkSelectionPanel';
|
|||||||
|
|
||||||
export default function RightRail() {
|
export default function RightRail() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const isPanning = usePanState();
|
||||||
const { toggleTheme } = useRainbowThemeContext();
|
const { toggleTheme } = useRainbowThemeContext();
|
||||||
const { buttons, actions } = useRightRail();
|
const { buttons, actions } = useRightRail();
|
||||||
const topButtons = useMemo(() => buttons.filter(b => (b.section || 'top') === 'top' && (b.visible ?? true)), [buttons]);
|
const topButtons = useMemo(() => buttons.filter(b => (b.section || 'top') === 'top' && (b.visible ?? true)), [buttons]);
|
||||||
@ -265,10 +267,11 @@ export default function RightRail() {
|
|||||||
{/* Pan Mode */}
|
{/* Pan Mode */}
|
||||||
<Tooltip content={t('rightRail.panMode', 'Pan Mode')} position="left" offset={12} arrow>
|
<Tooltip content={t('rightRail.panMode', 'Pan Mode')} position="left" offset={12} arrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="subtle"
|
variant={isPanning ? "filled" : "subtle"}
|
||||||
|
color={isPanning ? "blue" : undefined}
|
||||||
radius="md"
|
radius="md"
|
||||||
className="right-rail-icon"
|
className="right-rail-icon"
|
||||||
onClick={() => (window as any).embedPdfControls?.pan()}
|
onClick={() => (window as any).embedPdfPan?.togglePan()}
|
||||||
disabled={currentView !== 'viewer'}
|
disabled={currentView !== 'viewer'}
|
||||||
>
|
>
|
||||||
<LocalIcon icon="pan-tool-rounded" width="1.5rem" height="1.5rem" />
|
<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 { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
|
||||||
import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
||||||
import { ZoomPluginPackage, ZoomMode } from '@embedpdf/plugin-zoom/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 { SelectionLayer, SelectionPluginPackage } from '@embedpdf/plugin-selection/react';
|
||||||
import { TilingLayer, TilingPluginPackage } from '@embedpdf/plugin-tiling/react';
|
import { TilingLayer, TilingPluginPackage } from '@embedpdf/plugin-tiling/react';
|
||||||
|
import { PanPluginPackage } from '@embedpdf/plugin-pan/react';
|
||||||
import { ZoomControlsExporter } from './ZoomControlsExporter';
|
import { ZoomControlsExporter } from './ZoomControlsExporter';
|
||||||
import { ScrollControlsExporter } from './ScrollControlsExporter';
|
import { ScrollControlsExporter } from './ScrollControlsExporter';
|
||||||
import { SelectionControlsExporter } from './SelectionControlsExporter';
|
import { SelectionControlsExporter } from './SelectionControlsExporter';
|
||||||
|
import { PanControlsExporter } from './PanControlsExporter';
|
||||||
|
|
||||||
interface LocalEmbedPDFProps {
|
interface LocalEmbedPDFProps {
|
||||||
file?: File | Blob;
|
file?: File | Blob;
|
||||||
@ -68,6 +70,11 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
|
|||||||
// Register selection plugin (depends on InteractionManager)
|
// Register selection plugin (depends on InteractionManager)
|
||||||
createPluginRegistration(SelectionPluginPackage),
|
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
|
// Register zoom plugin with configuration
|
||||||
createPluginRegistration(ZoomPluginPackage, {
|
createPluginRegistration(ZoomPluginPackage, {
|
||||||
defaultZoomLevel: ZoomMode.FitPage,
|
defaultZoomLevel: ZoomMode.FitPage,
|
||||||
@ -158,36 +165,55 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
|
|||||||
<ZoomControlsExporter />
|
<ZoomControlsExporter />
|
||||||
<ScrollControlsExporter />
|
<ScrollControlsExporter />
|
||||||
<SelectionControlsExporter />
|
<SelectionControlsExporter />
|
||||||
<Viewport
|
<PanControlsExporter />
|
||||||
style={{
|
<GlobalPointerProvider>
|
||||||
backgroundColor: actualColorScheme === 'dark' ? '#1a1b1e' : '#f1f3f5',
|
<Viewport
|
||||||
height: '100%',
|
style={{
|
||||||
width: '100%',
|
backgroundColor: actualColorScheme === 'dark' ? '#1a1b1e' : '#f1f3f5',
|
||||||
maxHeight: '100%',
|
height: '100%',
|
||||||
maxWidth: '100%',
|
width: '100%',
|
||||||
overflow: 'auto',
|
maxHeight: '100%',
|
||||||
position: 'relative',
|
maxWidth: '100%',
|
||||||
flex: 1,
|
overflow: 'auto',
|
||||||
minHeight: 0,
|
position: 'relative',
|
||||||
minWidth: 0,
|
flex: 1,
|
||||||
contain: 'strict',
|
minHeight: 0,
|
||||||
}}
|
minWidth: 0,
|
||||||
>
|
contain: 'strict',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Scroller
|
<Scroller
|
||||||
renderPage={({ width, height, pageIndex, scale, rotation }: { width: number; height: number; pageIndex: number; scale: number; rotation?: number }) => (
|
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 }}>
|
||||||
{/* 1. Low-resolution base layer for immediate feedback */}
|
<div
|
||||||
<RenderLayer pageIndex={pageIndex} scale={0.5} />
|
style={{
|
||||||
|
width,
|
||||||
{/* 2. High-resolution tile layer on top */}
|
height,
|
||||||
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
position: 'relative',
|
||||||
|
userSelect: 'none',
|
||||||
{/* 3. Selection layer for text interaction */}
|
WebkitUserSelect: 'none',
|
||||||
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
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>
|
</PagePointerProvider>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Viewport>
|
</Viewport>
|
||||||
|
</GlobalPointerProvider>
|
||||||
</EmbedPDF>
|
</EmbedPDF>
|
||||||
</div>
|
</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