first commit
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import useEventListener from '../../../shared/hooks/use-event-listener'
|
||||
import useDetachAction from '../../../shared/hooks/use-detach-action'
|
||||
|
||||
export const startCompileKeypress = event => {
|
||||
if (event.shiftKey || event.altKey) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (event.ctrlKey) {
|
||||
// Ctrl+s / Ctrl+Enter / Ctrl+.
|
||||
if (event.key === 's' || event.key === 'Enter' || event.key === '.') {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ctrl+s with Caps-Lock on
|
||||
if (event.key === 'S' && !event.shiftKey) {
|
||||
return true
|
||||
}
|
||||
} else if (event.metaKey) {
|
||||
// Cmd+s / Cmd+Enter
|
||||
if (event.key === 's' || event.key === 'Enter') {
|
||||
return true
|
||||
}
|
||||
|
||||
// Cmd+s with Caps-Lock on
|
||||
if (event.key === 'S' && !event.shiftKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function useCompileTriggers(startCompile, setChangedAt) {
|
||||
const handleKeyDown = useCallback(
|
||||
event => {
|
||||
if (startCompileKeypress(event)) {
|
||||
event.preventDefault()
|
||||
startCompile()
|
||||
}
|
||||
},
|
||||
[startCompile]
|
||||
)
|
||||
|
||||
const handleStartCompile = useCallback(() => {
|
||||
startCompile()
|
||||
}, [startCompile])
|
||||
useEventListener('pdf:recompile', handleStartCompile)
|
||||
|
||||
useEffect(() => {
|
||||
document.body.addEventListener('keydown', handleKeyDown)
|
||||
return () => {
|
||||
document.body.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [handleKeyDown])
|
||||
|
||||
// record doc changes when notified by the editor
|
||||
const setOrTriggerChangedAt = useDetachAction(
|
||||
'set-changed-at',
|
||||
setChangedAt,
|
||||
'detacher',
|
||||
'detached'
|
||||
)
|
||||
const setChangedAtHandler = useCallback(() => {
|
||||
setOrTriggerChangedAt(Date.now())
|
||||
}, [setOrTriggerChangedAt])
|
||||
useEventListener('doc:changed', setChangedAtHandler)
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
|
||||
/**
|
||||
* This hook adds an event listener for events dispatched from the editor to the compile logs pane
|
||||
*/
|
||||
export const useLogEvents = (setShowLogs: (show: boolean) => void) => {
|
||||
const { pdfLayout, setView } = useLayoutContext()
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (event: Event) => {
|
||||
const { id, suggestFix } = (
|
||||
event as CustomEvent<{ id: string; suggestFix?: boolean }>
|
||||
).detail
|
||||
|
||||
setShowLogs(true)
|
||||
|
||||
if (pdfLayout === 'flat') {
|
||||
setView('pdf')
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
const element = document.querySelector(
|
||||
`.log-entry[data-log-entry-id="${id}"]`
|
||||
)
|
||||
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
block: 'start',
|
||||
inline: 'nearest',
|
||||
})
|
||||
|
||||
if (suggestFix) {
|
||||
// if they are paywalled, click that instead
|
||||
const paywall = document.querySelector<HTMLButtonElement>(
|
||||
'button[data-action="assistant-paywall-show"]'
|
||||
)
|
||||
|
||||
if (paywall) {
|
||||
paywall.scrollIntoView({
|
||||
block: 'start',
|
||||
inline: 'nearest',
|
||||
})
|
||||
paywall.click()
|
||||
} else {
|
||||
element
|
||||
.querySelector<HTMLButtonElement>(
|
||||
'button[data-action="suggest-fix"]'
|
||||
)
|
||||
?.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('editor:view-compile-log-entry', listener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('editor:view-compile-log-entry', listener)
|
||||
}
|
||||
}, [pdfLayout, setView, setShowLogs])
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import PDFJSWrapper from '../util/pdf-js-wrapper'
|
||||
|
||||
// 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 isZoomingRef = useRef(false)
|
||||
|
||||
// To avoid accidental pdf when pressing CMD/CTRL when the pdf scroll still has
|
||||
// momentum, we only zoom if CMD/CTRL is pressed before the scroll starts. These refs
|
||||
// keep track of if the pdf is currently scrolling.
|
||||
// https://github.com/overleaf/internal/issues/20772
|
||||
const isScrollingRef = useRef(false)
|
||||
const isScrollingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null
|
||||
)
|
||||
|
||||
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) {
|
||||
const wheelListener = (event: WheelEvent) => {
|
||||
if ((event.metaKey || event.ctrlKey) && !isScrollingRef.current) {
|
||||
event.preventDefault()
|
||||
|
||||
if (!isZoomingRef.current) {
|
||||
isZoomingRef.current = true
|
||||
|
||||
performZoom(event, pdfJsWrapper)
|
||||
|
||||
setTimeout(() => {
|
||||
isZoomingRef.current = false
|
||||
}, 5)
|
||||
}
|
||||
} else {
|
||||
isScrollingRef.current = true
|
||||
if (isScrollingTimeoutRef.current) {
|
||||
clearTimeout(isScrollingTimeoutRef.current)
|
||||
}
|
||||
|
||||
isScrollingTimeoutRef.current = setTimeout(() => {
|
||||
isScrollingRef.current = false
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
pdfJsWrapper.container.addEventListener('wheel', wheelListener)
|
||||
|
||||
return () => {
|
||||
pdfJsWrapper.container.removeEventListener('wheel', wheelListener)
|
||||
}
|
||||
}
|
||||
}, [pdfJsWrapper, setScale, performZoom])
|
||||
}
|
@@ -0,0 +1,176 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import PDFJSWrapper from '../util/pdf-js-wrapper'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
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)
|
||||
|
||||
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]
|
||||
)
|
||||
|
||||
const arrowKeyListener = useCallback(
|
||||
event => {
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowUp':
|
||||
case 'PageUp':
|
||||
case 'Backspace':
|
||||
previousPage()
|
||||
break
|
||||
|
||||
case 'ArrowRight':
|
||||
case 'ArrowDown':
|
||||
case 'PageDown':
|
||||
nextPage()
|
||||
break
|
||||
|
||||
case ' ':
|
||||
if (event.shiftKey) {
|
||||
previousPage()
|
||||
} else {
|
||||
nextPage()
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
[nextPage, previousPage]
|
||||
)
|
||||
|
||||
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]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (presentationMode) {
|
||||
window.addEventListener('keydown', arrowKeyListener)
|
||||
window.addEventListener('click', clickListener)
|
||||
window.addEventListener('wheel', mouseWheelListener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', arrowKeyListener)
|
||||
window.removeEventListener('click', clickListener)
|
||||
window.removeEventListener('wheel', mouseWheelListener)
|
||||
}
|
||||
}
|
||||
}, [presentationMode, arrowKeyListener, clickListener, mouseWheelListener])
|
||||
|
||||
const requestPresentationMode = useCallback(() => {
|
||||
sendMB('pdf-viewer-enter-presentation-mode')
|
||||
|
||||
if (pdfJsWrapper) {
|
||||
pdfJsWrapper.container.parentElement
|
||||
?.requestFullscreen()
|
||||
.catch(debugConsole.error)
|
||||
}
|
||||
}, [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
|
||||
|
||||
pdfJsWrapper.fetchAllData()
|
||||
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user