2024-06-14 08:14:23 -04:00
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
|
import PDFJSWrapper from '../util/pdf-js-wrapper'
|
2024-06-25 04:51:12 -04:00
|
|
|
import { sendMB } from '@/infrastructure/event-tracking'
|
2024-06-14 08:14:23 -04:00
|
|
|
|
|
|
|
type StoredPDFState = {
|
|
|
|
scrollMode?: number
|
|
|
|
spreadMode?: number
|
|
|
|
currentScaleValue?: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function usePresentationMode(
|
|
|
|
pdfJsWrapper: PDFJSWrapper | null | undefined,
|
|
|
|
page: number | null,
|
|
|
|
handlePageChange: (page: number) => void,
|
|
|
|
scale: string,
|
|
|
|
setScale: (scale: string) => void
|
|
|
|
): () => void {
|
|
|
|
const storedState = useRef<StoredPDFState>({})
|
|
|
|
|
|
|
|
const [presentationMode, setPresentationMode] = useState(false)
|
|
|
|
|
2024-06-20 08:12:01 -04:00
|
|
|
const nextPage = useCallback(() => {
|
|
|
|
if (page !== null) {
|
|
|
|
handlePageChange(page + 1)
|
|
|
|
}
|
|
|
|
}, [handlePageChange, page])
|
|
|
|
|
|
|
|
const previousPage = useCallback(() => {
|
|
|
|
if (page !== null) {
|
|
|
|
handlePageChange(page - 1)
|
|
|
|
}
|
|
|
|
}, [handlePageChange, page])
|
|
|
|
|
|
|
|
const clickListener = useCallback(
|
|
|
|
event => {
|
|
|
|
if (event.target.tagName === 'A') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.shiftKey) {
|
|
|
|
previousPage()
|
|
|
|
} else {
|
|
|
|
nextPage()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[nextPage, previousPage]
|
|
|
|
)
|
|
|
|
|
2024-06-14 08:14:23 -04:00
|
|
|
const arrowKeyListener = useCallback(
|
|
|
|
event => {
|
2024-06-20 08:12:01 -04:00
|
|
|
switch (event.key) {
|
|
|
|
case 'ArrowLeft':
|
|
|
|
case 'ArrowUp':
|
2024-06-25 04:51:26 -04:00
|
|
|
case 'PageUp':
|
|
|
|
case 'Backspace':
|
2024-06-20 08:12:01 -04:00
|
|
|
previousPage()
|
|
|
|
break
|
|
|
|
|
|
|
|
case 'ArrowRight':
|
|
|
|
case 'ArrowDown':
|
2024-06-25 04:51:26 -04:00
|
|
|
case 'PageDown':
|
2024-06-20 08:12:01 -04:00
|
|
|
nextPage()
|
|
|
|
break
|
|
|
|
|
|
|
|
case ' ':
|
|
|
|
if (event.shiftKey) {
|
|
|
|
previousPage()
|
|
|
|
} else {
|
|
|
|
nextPage()
|
|
|
|
}
|
|
|
|
break
|
2024-06-14 08:14:23 -04:00
|
|
|
}
|
|
|
|
},
|
2024-06-20 08:12:01 -04:00
|
|
|
[nextPage, previousPage]
|
2024-06-14 08:14:23 -04:00
|
|
|
)
|
|
|
|
|
2024-08-19 09:17:04 -04:00
|
|
|
const isMouseWheelScrollingRef = useRef(false)
|
|
|
|
|
|
|
|
const mouseWheelListener = useCallback(
|
|
|
|
(event: WheelEvent) => {
|
|
|
|
if (
|
|
|
|
!isMouseWheelScrollingRef.current &&
|
|
|
|
!event.ctrlKey // Avoid trackpad pinching
|
|
|
|
) {
|
|
|
|
isMouseWheelScrollingRef.current = true
|
|
|
|
|
|
|
|
if (event.deltaY > 0) {
|
|
|
|
nextPage()
|
|
|
|
} else {
|
|
|
|
previousPage()
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
isMouseWheelScrollingRef.current = false
|
|
|
|
}, 200)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[nextPage, previousPage]
|
|
|
|
)
|
|
|
|
|
2024-06-14 08:14:23 -04:00
|
|
|
useEffect(() => {
|
|
|
|
if (presentationMode) {
|
|
|
|
window.addEventListener('keydown', arrowKeyListener)
|
2024-06-20 08:12:01 -04:00
|
|
|
window.addEventListener('click', clickListener)
|
2024-08-19 09:17:04 -04:00
|
|
|
window.addEventListener('wheel', mouseWheelListener)
|
2024-06-14 08:14:23 -04:00
|
|
|
|
|
|
|
return () => {
|
|
|
|
window.removeEventListener('keydown', arrowKeyListener)
|
2024-06-20 08:12:01 -04:00
|
|
|
window.removeEventListener('click', clickListener)
|
2024-08-19 09:17:04 -04:00
|
|
|
window.removeEventListener('wheel', mouseWheelListener)
|
2024-06-14 08:14:23 -04:00
|
|
|
}
|
|
|
|
}
|
2024-08-19 09:17:04 -04:00
|
|
|
}, [presentationMode, arrowKeyListener, clickListener, mouseWheelListener])
|
2024-06-14 08:14:23 -04:00
|
|
|
|
|
|
|
const requestPresentationMode = useCallback(() => {
|
2024-06-25 04:51:12 -04:00
|
|
|
sendMB('pdf-viewer-enter-presentation-mode')
|
|
|
|
|
2024-06-14 08:14:23 -04:00
|
|
|
if (pdfJsWrapper) {
|
|
|
|
pdfJsWrapper.container.parentNode.requestFullscreen()
|
|
|
|
}
|
|
|
|
}, [pdfJsWrapper])
|
|
|
|
|
|
|
|
const handleEnterFullscreen = useCallback(() => {
|
|
|
|
if (pdfJsWrapper) {
|
|
|
|
storedState.current.scrollMode = pdfJsWrapper.viewer.scrollMode
|
|
|
|
storedState.current.spreadMode = pdfJsWrapper.viewer.spreadMode
|
|
|
|
storedState.current.currentScaleValue = scale
|
|
|
|
|
|
|
|
setScale('page-fit')
|
|
|
|
pdfJsWrapper.viewer.scrollMode = 3 // page
|
|
|
|
pdfJsWrapper.viewer.spreadMode = 0 // none
|
|
|
|
|
|
|
|
setPresentationMode(true)
|
|
|
|
}
|
|
|
|
}, [pdfJsWrapper, setScale, scale])
|
|
|
|
|
|
|
|
const handleExitFullscreen = useCallback(() => {
|
|
|
|
if (pdfJsWrapper) {
|
|
|
|
pdfJsWrapper.viewer.scrollMode = storedState.current.scrollMode
|
|
|
|
pdfJsWrapper.viewer.spreadMode = storedState.current.spreadMode
|
|
|
|
|
|
|
|
if (storedState.current.currentScaleValue !== undefined) {
|
|
|
|
setScale(storedState.current.currentScaleValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
setPresentationMode(false)
|
|
|
|
}
|
|
|
|
}, [pdfJsWrapper, setScale])
|
|
|
|
|
|
|
|
const handleFullscreenChange = useCallback(() => {
|
|
|
|
if (pdfJsWrapper) {
|
|
|
|
const fullscreen =
|
|
|
|
document.fullscreenElement === pdfJsWrapper.container.parentNode
|
|
|
|
|
|
|
|
if (fullscreen) {
|
|
|
|
handleEnterFullscreen()
|
|
|
|
} else {
|
|
|
|
handleExitFullscreen()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [pdfJsWrapper, handleEnterFullscreen, handleExitFullscreen])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
window.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
|
|
return () => {
|
|
|
|
window.removeEventListener('fullscreenchange', handleFullscreenChange)
|
|
|
|
}
|
|
|
|
}, [handleFullscreenChange])
|
|
|
|
|
|
|
|
return requestPresentationMode
|
|
|
|
}
|