|
|
@@ -0,0 +1,89 @@
|
|
|
+import { useCallback, useEffect, useRef } from 'react'
|
|
|
+import PDFJSWrapper from '../util/pdf-js-wrapper'
|
|
|
+import { useFeatureFlag } from '@/shared/context/split-test-context'
|
|
|
+
|
|
|
+// We need this to work for both a traditional mouse wheel and a touchpad "pinch to zoom".
|
|
|
+// From experimentation, trackpads tend to fire a lot of events with small deltaY's where
|
|
|
+// as a mouse wheel will fire fewer events but sometimes with a very high deltaY if you
|
|
|
+// move the wheel quickly.
|
|
|
+// The divisor is set to a value that works for the trackpad with the maximum value ensuring
|
|
|
+// that the scale doesn't suddenly change drastically from moving the mouse wheel quickly.
|
|
|
+const MAX_SCALE_FACTOR = 1.2
|
|
|
+const SCALE_FACTOR_DIVISOR = 20
|
|
|
+
|
|
|
+export default function useMouseWheelZoom(
|
|
|
+ pdfJsWrapper: PDFJSWrapper | null | undefined,
|
|
|
+ setScale: (scale: string) => void
|
|
|
+) {
|
|
|
+ const isEnabled = useFeatureFlag('pdf-controls')
|
|
|
+
|
|
|
+ const isZoomingRef = useRef(false)
|
|
|
+
|
|
|
+ const performZoom = useCallback(
|
|
|
+ (event: WheelEvent, pdfJsWrapper: PDFJSWrapper) => {
|
|
|
+ // First, we calculate and set the new scale
|
|
|
+ const scrollMagnitude = Math.abs(event.deltaY)
|
|
|
+ const scaleFactorMagnitude = Math.min(
|
|
|
+ 1 + scrollMagnitude / SCALE_FACTOR_DIVISOR,
|
|
|
+ MAX_SCALE_FACTOR
|
|
|
+ )
|
|
|
+ const previousScale = pdfJsWrapper.viewer.currentScale
|
|
|
+ const scaleChangeDirection = Math.sign(event.deltaY)
|
|
|
+
|
|
|
+ const approximateScaleFactor =
|
|
|
+ scaleChangeDirection < 0
|
|
|
+ ? scaleFactorMagnitude
|
|
|
+ : 1 / scaleFactorMagnitude
|
|
|
+
|
|
|
+ const newScale =
|
|
|
+ Math.round(previousScale * approximateScaleFactor * 100) / 100
|
|
|
+ const exactScaleFactor = newScale / previousScale
|
|
|
+
|
|
|
+ // Set the scale directly to ensure it is set before we do the scrolling below
|
|
|
+ pdfJsWrapper.viewer.currentScale = newScale
|
|
|
+ setScale(`${newScale}`)
|
|
|
+
|
|
|
+ // Then we need to ensure we are centering the zoom on the mouse position
|
|
|
+ const containerRect = pdfJsWrapper.container.getBoundingClientRect()
|
|
|
+ const top = containerRect.top
|
|
|
+ const left = containerRect.left
|
|
|
+
|
|
|
+ // Positions relative to pdf viewer
|
|
|
+ const currentMouseX = event.clientX - left
|
|
|
+ const currentMouseY = event.clientY - top
|
|
|
+
|
|
|
+ pdfJsWrapper.container.scrollBy({
|
|
|
+ left: currentMouseX * exactScaleFactor - currentMouseX,
|
|
|
+ top: currentMouseY * exactScaleFactor - currentMouseY,
|
|
|
+ behavior: 'instant',
|
|
|
+ })
|
|
|
+ },
|
|
|
+ [setScale]
|
|
|
+ )
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (pdfJsWrapper && isEnabled) {
|
|
|
+ const wheelListener = (event: WheelEvent) => {
|
|
|
+ if (event.metaKey || event.ctrlKey) {
|
|
|
+ event.preventDefault()
|
|
|
+
|
|
|
+ if (!isZoomingRef.current) {
|
|
|
+ isZoomingRef.current = true
|
|
|
+
|
|
|
+ performZoom(event, pdfJsWrapper)
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ isZoomingRef.current = false
|
|
|
+ }, 5)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pdfJsWrapper.container.addEventListener('wheel', wheelListener)
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ pdfJsWrapper.container.removeEventListener('wheel', wheelListener)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, [pdfJsWrapper, setScale, isEnabled, performZoom])
|
|
|
+}
|