From 881557512438cf23a18c381c81718f0fa70657a1 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Thu, 11 Sep 2025 22:51:10 +0100 Subject: [PATCH] pan --- frontend/package-lock.json | 18 +++++ frontend/package.json | 1 + frontend/src/components/shared/RightRail.tsx | 7 +- .../src/components/viewer/LocalEmbedPDF.tsx | 78 ++++++++++++------- .../components/viewer/PanControlsExporter.tsx | 55 +++++++++++++ frontend/src/hooks/usePanState.ts | 24 ++++++ 6 files changed, 155 insertions(+), 28 deletions(-) create mode 100644 frontend/src/components/viewer/PanControlsExporter.tsx create mode 100644 frontend/src/hooks/usePanState.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 947c4c8bc..ad99f086c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index 5aea80510..2ffffea8e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/components/shared/RightRail.tsx b/frontend/src/components/shared/RightRail.tsx index 1a5526627..6d2b7a998 100644 --- a/frontend/src/components/shared/RightRail.tsx +++ b/frontend/src/components/shared/RightRail.tsx @@ -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 */} (window as any).embedPdfControls?.pan()} + onClick={() => (window as any).embedPdfPan?.togglePan()} disabled={currentView !== 'viewer'} > diff --git a/frontend/src/components/viewer/LocalEmbedPDF.tsx b/frontend/src/components/viewer/LocalEmbedPDF.tsx index 0f445b809..9a0423609 100644 --- a/frontend/src/components/viewer/LocalEmbedPDF.tsx +++ b/frontend/src/components/viewer/LocalEmbedPDF.tsx @@ -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) { - + + + ( - - {/* 1. Low-resolution base layer for immediate feedback */} - - - {/* 2. High-resolution tile layer on top */} - - - {/* 3. Selection layer for text interaction */} - + +
e.preventDefault()} + onDrop={(e) => e.preventDefault()} + onDragOver={(e) => e.preventDefault()} + > + {/* 1. Low-resolution base layer for immediate feedback */} + + + {/* 2. High-resolution tile layer on top */} + + + {/* 3. Selection layer for text interaction */} + +
)} /> -
+
+ ); diff --git a/frontend/src/components/viewer/PanControlsExporter.tsx b/frontend/src/components/viewer/PanControlsExporter.tsx new file mode 100644 index 000000000..6ed41bc29 --- /dev/null +++ b/frontend/src/components/viewer/PanControlsExporter.tsx @@ -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 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 +} \ No newline at end of file diff --git a/frontend/src/hooks/usePanState.ts b/frontend/src/hooks/usePanState.ts new file mode 100644 index 000000000..424c26600 --- /dev/null +++ b/frontend/src/hooks/usePanState.ts @@ -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; +} \ No newline at end of file