mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-20 22:42:40 +00:00
Merge pull request #18895 from overleaf/dp-presentation-mode
Add pdf presentation mode GitOrigin-RevId: e6ac1ae339e9690a733a110c6f0a33149e869dd6
This commit is contained in:
parent
805ec8c2e0
commit
dcb7944b05
8 changed files with 173 additions and 8 deletions
|
@ -84,7 +84,6 @@ const httpPermissionsPolicy = {
|
|||
'camera',
|
||||
'display-capture',
|
||||
'encrypted-media',
|
||||
'fullscreen',
|
||||
'gamepad',
|
||||
'geolocation',
|
||||
'gyroscope',
|
||||
|
@ -107,6 +106,7 @@ const httpPermissionsPolicy = {
|
|||
],
|
||||
allowed: {
|
||||
autoplay: 'self "https://videos.ctfassets.net"',
|
||||
fullscreen: 'self',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -935,6 +935,7 @@
|
|||
"postal_code": "",
|
||||
"premium_feature": "",
|
||||
"premium_plan_label": "",
|
||||
"present": "",
|
||||
"previous_page": "",
|
||||
"price": "",
|
||||
"primarily_work_study_question": "",
|
||||
|
@ -1635,6 +1636,7 @@
|
|||
"youve_unlinked_all_users": "",
|
||||
"zoom_in": "",
|
||||
"zoom_out": "",
|
||||
"zoom_to": "",
|
||||
"zotero_cta": "",
|
||||
"zotero_groups_loading_error": "",
|
||||
"zotero_groups_relink": "",
|
||||
|
|
|
@ -15,6 +15,7 @@ import { getPdfCachingMetrics } from '../util/metrics'
|
|||
import { debugConsole } from '@/utils/debugging'
|
||||
import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import usePresentationMode from '../hooks/use-presentation-mode'
|
||||
|
||||
type PdfJsViewerProps = {
|
||||
url: string
|
||||
|
@ -48,13 +49,17 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
|
|||
const [initialised, setInitialised] = useState(false)
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
newPage => {
|
||||
(newPage: number) => {
|
||||
if (!totalPages || newPage < 1 || newPage > totalPages) {
|
||||
return
|
||||
}
|
||||
|
||||
setPage(newPage)
|
||||
if (pdfJsWrapper?.viewer) {
|
||||
pdfJsWrapper.viewer.currentPageNumber = newPage
|
||||
}
|
||||
},
|
||||
[pdfJsWrapper, setPage]
|
||||
[pdfJsWrapper, setPage, totalPages]
|
||||
)
|
||||
|
||||
// create the viewer when the container is mounted
|
||||
|
@ -461,6 +466,14 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
|
|||
[initialised, setZoom]
|
||||
)
|
||||
|
||||
const requestPresentationMode = usePresentationMode(
|
||||
pdfJsWrapper,
|
||||
page,
|
||||
handlePageChange,
|
||||
scale,
|
||||
setScale
|
||||
)
|
||||
|
||||
// Don't render the toolbar until we have the necessary information
|
||||
const toolbarInfoLoaded =
|
||||
rawScale !== null && page !== null && totalPages !== null
|
||||
|
@ -487,6 +500,7 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
|
|||
{hasNewPdfToolbar ? (
|
||||
toolbarInfoLoaded && (
|
||||
<PdfViewerControlsToolbar
|
||||
requestPresentationMode={requestPresentationMode}
|
||||
setZoom={setZoom}
|
||||
rawScale={rawScale}
|
||||
setPage={handlePageChange}
|
||||
|
|
|
@ -9,6 +9,7 @@ import PdfViewerControlsMenuButton from './pdf-viewer-controls-menu-button'
|
|||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
|
||||
type PdfViewerControlsToolbarProps = {
|
||||
requestPresentationMode: () => void
|
||||
setZoom: (zoom: string) => void
|
||||
rawScale: number
|
||||
setPage: (page: number) => void
|
||||
|
@ -17,6 +18,7 @@ type PdfViewerControlsToolbarProps = {
|
|||
}
|
||||
|
||||
function PdfViewerControlsToolbar({
|
||||
requestPresentationMode,
|
||||
setZoom,
|
||||
rawScale,
|
||||
setPage,
|
||||
|
@ -54,6 +56,7 @@ function PdfViewerControlsToolbar({
|
|||
return createPortal(
|
||||
<div className="pdfjs-viewer-controls" ref={pdfControlsRef}>
|
||||
<InnerControlsComponent
|
||||
requestPresentationMode={requestPresentationMode}
|
||||
setZoom={setZoom}
|
||||
rawScale={rawScale}
|
||||
setPage={setPage}
|
||||
|
@ -67,6 +70,7 @@ function PdfViewerControlsToolbar({
|
|||
}
|
||||
|
||||
type InnerControlsProps = {
|
||||
requestPresentationMode: () => void
|
||||
setZoom: (zoom: string) => void
|
||||
rawScale: number
|
||||
setPage: (page: number) => void
|
||||
|
@ -75,6 +79,7 @@ type InnerControlsProps = {
|
|||
}
|
||||
|
||||
function PdfViewerControlsToolbarFull({
|
||||
requestPresentationMode,
|
||||
setZoom,
|
||||
rawScale,
|
||||
setPage,
|
||||
|
@ -90,13 +95,18 @@ function PdfViewerControlsToolbarFull({
|
|||
/>
|
||||
<div className="pdfjs-zoom-controls">
|
||||
<PdfZoomButtons setZoom={setZoom} />
|
||||
<PdfZoomDropdown rawScale={rawScale} setZoom={setZoom} />
|
||||
<PdfZoomDropdown
|
||||
requestPresentationMode={requestPresentationMode}
|
||||
rawScale={rawScale}
|
||||
setZoom={setZoom}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function PdfViewerControlsToolbarSmall({
|
||||
requestPresentationMode,
|
||||
setZoom,
|
||||
rawScale,
|
||||
setPage,
|
||||
|
@ -105,7 +115,11 @@ function PdfViewerControlsToolbarSmall({
|
|||
}: InnerControlsProps) {
|
||||
return (
|
||||
<div className="pdfjs-viewer-controls-small">
|
||||
<PdfZoomDropdown rawScale={rawScale} setZoom={setZoom} />
|
||||
<PdfZoomDropdown
|
||||
requestPresentationMode={requestPresentationMode}
|
||||
rawScale={rawScale}
|
||||
setZoom={setZoom}
|
||||
/>
|
||||
<PdfViewerControlsMenuButton
|
||||
setZoom={setZoom}
|
||||
setPage={setPage}
|
||||
|
|
|
@ -19,6 +19,7 @@ const shortcuts = isMac
|
|||
}
|
||||
|
||||
type PdfZoomDropdownProps = {
|
||||
requestPresentationMode: () => void
|
||||
setZoom: (zoom: string) => void
|
||||
rawScale: number
|
||||
}
|
||||
|
@ -29,7 +30,11 @@ const rawScaleToPercentage = (rawScale: number) => {
|
|||
return `${Math.round(rawScale * 100)}%`
|
||||
}
|
||||
|
||||
function PdfZoomDropdown({ setZoom, rawScale }: PdfZoomDropdownProps) {
|
||||
function PdfZoomDropdown({
|
||||
requestPresentationMode,
|
||||
setZoom,
|
||||
rawScale,
|
||||
}: PdfZoomDropdownProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [customZoomValue, setCustomZoomValue] = useState<string>(
|
||||
|
@ -44,7 +49,16 @@ function PdfZoomDropdown({ setZoom, rawScale }: PdfZoomDropdownProps) {
|
|||
<ControlledDropdown
|
||||
id="pdf-zoom-dropdown"
|
||||
onSelect={eventKey => {
|
||||
if (eventKey !== 'custom-zoom') setZoom(eventKey)
|
||||
if (eventKey === 'custom-zoom') {
|
||||
return
|
||||
}
|
||||
|
||||
if (eventKey === 'present') {
|
||||
requestPresentationMode()
|
||||
return
|
||||
}
|
||||
|
||||
setZoom(eventKey)
|
||||
}}
|
||||
pullRight
|
||||
>
|
||||
|
@ -102,7 +116,14 @@ function PdfZoomDropdown({ setZoom, rawScale }: PdfZoomDropdownProps) {
|
|||
<MenuItem draggable={false} key="page-height" eventKey="page-height">
|
||||
{t('fit_to_height')}
|
||||
</MenuItem>
|
||||
{document.fullscreenEnabled && <MenuItem divider />}
|
||||
{document.fullscreenEnabled && (
|
||||
<MenuItem draggable={false} key="present" eventKey="present">
|
||||
{t('present')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem divider />
|
||||
<MenuItem header>{t('zoom_to')}</MenuItem>
|
||||
{zoomValues.map(value => (
|
||||
<MenuItem draggable={false} key={value} eventKey={value}>
|
||||
{rawScaleToPercentage(Number(value))}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import PDFJSWrapper from '../util/pdf-js-wrapper'
|
||||
|
||||
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 arrowKeyListener = useCallback(
|
||||
event => {
|
||||
if (page !== null) {
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowUp':
|
||||
handlePageChange(page - 1)
|
||||
break
|
||||
|
||||
case 'ArrowRight':
|
||||
case 'ArrowDown':
|
||||
handlePageChange(page + 1)
|
||||
break
|
||||
|
||||
case ' ':
|
||||
if (event.shiftKey) {
|
||||
handlePageChange(page - 1)
|
||||
} else {
|
||||
handlePageChange(page + 1)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
[page, handlePageChange]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (presentationMode) {
|
||||
window.addEventListener('keydown', arrowKeyListener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', arrowKeyListener)
|
||||
}
|
||||
}
|
||||
}, [presentationMode, arrowKeyListener])
|
||||
|
||||
const requestPresentationMode = useCallback(() => {
|
||||
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
|
||||
}
|
|
@ -1383,6 +1383,7 @@
|
|||
"premium_feature": "Premium feature",
|
||||
"premium_features": "Premium features",
|
||||
"premium_plan_label": "You’re using <b>Overleaf Premium</b>",
|
||||
"present": "Present",
|
||||
"presentation": "Presentation",
|
||||
"press_and_awards": "Press & awards",
|
||||
"previous_page": "Previous page",
|
||||
|
@ -2276,6 +2277,7 @@
|
|||
"zip_contents_too_large": "Zip contents too large",
|
||||
"zoom_in": "Zoom in",
|
||||
"zoom_out": "Zoom out",
|
||||
"zoom_to": "Zoom to",
|
||||
"zotero": "Zotero",
|
||||
"zotero_and_mendeley_integrations": "<0>Zotero</0> and <0>Mendeley</0> integrations",
|
||||
"zotero_cta": "Get Zotero integration",
|
||||
|
|
|
@ -9,7 +9,7 @@ describe('HttpPermissionsPolicy', function () {
|
|||
const response = await fetch(BASE_URL)
|
||||
|
||||
expect(response.headers.get('permissions-policy')).to.equal(
|
||||
'accelerometer=(), attribution-reporting=(), browsing-topics=(), camera=(), display-capture=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), screen-wake-lock=(), serial=(), storage-access=(), usb=(), window-management=(), xr-spatial-tracking=(), autoplay=(self "https://videos.ctfassets.net")'
|
||||
'accelerometer=(), attribution-reporting=(), browsing-topics=(), camera=(), display-capture=(), encrypted-media=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), screen-wake-lock=(), serial=(), storage-access=(), usb=(), window-management=(), xr-spatial-tracking=(), autoplay=(self "https://videos.ctfassets.net"), fullscreen=(self)'
|
||||
)
|
||||
})
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue