Merge pull request #18435 from overleaf/dp-ae-pdf-viewer-controls

Update PDF viewer controls

GitOrigin-RevId: 4e15b7cbd34e878d0175be635369b8d620188203
This commit is contained in:
David 2024-05-29 15:17:37 +01:00 committed by Copybot
parent e0f6622519
commit 55e54ce875
12 changed files with 721 additions and 48 deletions

View file

@ -328,6 +328,7 @@ const _ProjectController = {
'pdf-caching-mode',
'pdf-caching-prefetch-large',
'pdf-caching-prefetching',
'pdf-controls',
'pdfjs-40',
'personal-access-token',
'revert-file',

View file

@ -808,6 +808,7 @@
"newsletter": "",
"newsletter_onboarding_accept": "",
"next": "",
"next_page": "",
"next_payment_of_x_collectected_on_y": "",
"no_actions": "",
"no_borders": "",
@ -934,6 +935,7 @@
"postal_code": "",
"premium_feature": "",
"premium_plan_label": "",
"previous_page": "",
"price": "",
"primarily_work_study_question": "",
"primarily_work_study_question_company": "",
@ -1529,6 +1531,7 @@
"view_metrics_commons_subtext": "",
"view_metrics_group_subtext": "",
"view_more": "",
"view_options": "",
"view_pdf": "",
"view_your_invoices": "",
"viewing_x": "",

View file

@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import { memo, useCallback, useEffect, useRef, useState } from 'react'
import { debounce, throttle } from 'lodash'
import PdfViewerControls from './pdf-viewer-controls'
import PdfViewerControlsToolbar from './pdf-viewer-controls-toolbar'
import { useProjectContext } from '../../../shared/context/project-context'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import { buildHighlightElement } from '../util/highlights'
@ -14,6 +15,7 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
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'
function PdfJsViewer({ url, pdfFile }) {
const { _id: projectId } = useProjectContext()
@ -23,16 +25,34 @@ function PdfJsViewer({ url, pdfFile }) {
const { setLoadingError } = usePdfPreviewContext()
const hasNewPdfToolbar = useFeatureFlag('pdf-controls')
// state values persisted in localStorage to restore on load
const [scale, setScale] = usePersistedState(
`pdf-viewer-scale:${projectId}`,
'page-width'
)
// rawScale is different from scale as it is always a number.
// This is relevant when scale is e.g. 'page-width'.
const [rawScale, setRawScale] = useState(null)
const [page, setPage] = useState(null)
const [totalPages, setTotalPages] = useState(null)
// local state values
const [pdfJsWrapper, setPdfJsWrapper] = useState()
const [initialised, setInitialised] = useState(false)
const handlePageChange = useCallback(
newPage => {
setPage(newPage)
if (pdfJsWrapper?.viewer) {
pdfJsWrapper.viewer.currentPageNumber = newPage
}
},
[pdfJsWrapper, setPage]
)
// create the viewer when the container is mounted
const handleContainer = useCallback(
parent => {
@ -109,13 +129,27 @@ function PdfJsViewer({ url, pdfFile }) {
pdfJsWrapper.eventBus.off('pagerendered', handleRendered)
}
const handleRenderedInitialPageNumber = () => {
setPage(pdfJsWrapper.viewer.currentPageNumber)
}
const handleScaleChanged = () => {
setRawScale(pdfJsWrapper.viewer.currentScale)
}
// `pagesinit` fires when the data for rendering the first page is ready.
pdfJsWrapper.eventBus.on('pagesinit', handlePagesinit)
// `pagerendered` fires when a page was actually rendered.
pdfJsWrapper.eventBus.on('pagerendered', handleRendered)
// Once a page has been rendered we know the scale that it has been rendered to.
pdfJsWrapper.eventBus.on('pagerendered', handleScaleChanged)
// Once a page has been rendered we can set the initial current page number.
pdfJsWrapper.eventBus.on('pagerendered', handleRenderedInitialPageNumber)
return () => {
pdfJsWrapper.eventBus.off('pagesinit', handlePagesinit)
pdfJsWrapper.eventBus.off('pagerendered', handleRendered)
pdfJsWrapper.eventBus.off('pagerendered', handleScaleChanged)
pdfJsWrapper.eventBus.off('pagerendered', handleRenderedInitialPageNumber)
}
}, [pdfJsWrapper, firstRenderDone, startFetch])
@ -138,6 +172,9 @@ function PdfJsViewer({ url, pdfFile }) {
}
pdfJsWrapper
.loadDocument({ url, pdfFile, abortController, handleFetchError })
.then(doc => {
setTotalPages(doc.numPages)
})
.catch(error => {
if (abortController.signal.aborted) return
debugConsole.error(error)
@ -175,6 +212,7 @@ function PdfJsViewer({ url, pdfFile }) {
const scrollListener = () => {
storePosition(pdfJsWrapper)
setPage(pdfJsWrapper.viewer.currentPageNumber)
}
pdfJsWrapper.container.addEventListener('scroll', scrollListener)
@ -233,7 +271,6 @@ function PdfJsViewer({ url, pdfFile }) {
}
}, [pdfJsWrapper])
// restore the saved scale and scroll position
const positionRef = useRef(position)
useEffect(() => {
positionRef.current = position
@ -244,6 +281,7 @@ function PdfJsViewer({ url, pdfFile }) {
scaleRef.current = scale
}, [scale])
// restore the saved scale and scroll position
useEffect(() => {
if (initialised && pdfJsWrapper) {
if (!pdfJsWrapper.isVisible()) {
@ -332,6 +370,8 @@ function PdfJsViewer({ url, pdfFile }) {
const setZoom = useCallback(
zoom => {
switch (zoom) {
// TODO: We can remove fit-width and fit-height once the
// pdf toolbar is fully rolled out
case 'fit-width':
setScale('page-width')
break
@ -339,7 +379,6 @@ function PdfJsViewer({ url, pdfFile }) {
case 'fit-height':
setScale('page-height')
break
case 'zoom-in':
if (pdfJsWrapper) {
setScale(pdfJsWrapper.viewer.currentScale * 1.25)
@ -351,6 +390,9 @@ function PdfJsViewer({ url, pdfFile }) {
setScale(pdfJsWrapper.viewer.currentScale * 0.75)
}
break
default:
setScale(zoom)
}
},
[pdfJsWrapper, setScale]
@ -394,7 +436,7 @@ function PdfJsViewer({ url, pdfFile }) {
case '0':
event.preventDefault()
setZoom('fit-width')
setZoom('page-width')
break
}
}
@ -432,20 +474,37 @@ function PdfJsViewer({ url, pdfFile }) {
}
}, [pdfJsWrapper])
// Don't render the toolbar until we have the necessary information
const toolbarInfoLoaded =
rawScale !== null && page !== null && totalPages !== null
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
return (
<div className="pdfjs-viewer pdfjs-viewer-outer" ref={handleContainer}>
<div
className="pdfjs-viewer-inner"
role="tabpanel"
tabIndex="0"
onKeyDown={handleKeyDown}
>
<div
className="pdfjs-viewer pdfjs-viewer-outer"
ref={handleContainer}
onKeyDown={handleKeyDown}
role="tabpanel"
tabIndex={0}
>
<div className="pdfjs-viewer-inner">
<div className="pdfViewer" />
</div>
<div className="pdfjs-controls" tabIndex="0">
<PdfViewerControls setZoom={setZoom} />
{hasNewPdfToolbar ? (
toolbarInfoLoaded && (
<PdfViewerControlsToolbar
setZoom={setZoom}
rawScale={rawScale}
setPage={handlePageChange}
page={page}
totalPages={totalPages}
/>
)
) : (
<PdfViewerControls setZoom={setZoom} />
)}
</div>
</div>
)

View file

@ -0,0 +1,76 @@
import { ButtonGroup } from 'react-bootstrap'
import PDFToolbarButton from './pdf-toolbar-button'
import { useTranslation } from 'react-i18next'
import { useState, useEffect } from 'react'
type PdfPageNumberControlProps = {
setPage: (page: number) => void
page: number
totalPages: number
}
function PdfPageNumberControl({
setPage,
page,
totalPages,
}: PdfPageNumberControlProps) {
const { t } = useTranslation()
const [pageInputValue, setPageInputValue] = useState(page.toString())
useEffect(() => {
setPageInputValue(page.toString())
}, [page])
const handleSubmit = (event: React.SyntheticEvent) => {
event.preventDefault()
const parsedValue = Number(pageInputValue)
if (parsedValue < 1) {
setPage(1)
setPageInputValue('1')
} else if (parsedValue > totalPages) {
setPage(totalPages)
setPageInputValue(`${totalPages}`)
} else {
setPage(parsedValue)
}
}
return (
<>
<ButtonGroup className="pdfjs-toolbar-buttons ">
<PDFToolbarButton
tooltipId="pdf-controls-previous-page-tooltip"
icon="keyboard_arrow_up"
label={t('previous_page')}
disabled={page === 1}
onClick={() => setPage(page - 1)}
/>
<PDFToolbarButton
tooltipId="pdf-controls-next-page-tooltip"
icon="keyboard_arrow_down"
label={t('next_page')}
disabled={page === totalPages}
onClick={() => setPage(page + 1)}
/>
</ButtonGroup>
<div className="pdfjs-page-number-input">
<form onSubmit={handleSubmit}>
<input
inputMode="numeric"
value={pageInputValue}
onFocus={event => event.target.select()}
onBlur={handleSubmit}
onChange={event => {
const rawValue = event.target.value
setPageInputValue(rawValue.replace(/\D/g, ''))
}}
/>
</form>
<span>/ {totalPages}</span>
</div>
</>
)
}
export default PdfPageNumberControl

View file

@ -9,6 +9,7 @@ import PdfHybridDownloadButton from './pdf-hybrid-download-button'
import PdfHybridCodeCheckButton from './pdf-hybrid-code-check-button'
import PdfOrphanRefreshButton from './pdf-orphan-refresh-button'
import { DetachedSynctexControl } from './detach-synctex-control'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import Icon from '../../../shared/components/icon'
const ORPHAN_UI_TIMEOUT_MS = 5000
@ -54,6 +55,8 @@ function PdfPreviewHybridToolbar() {
}
function PdfPreviewHybridToolbarInner() {
const hasNewPdfToolbar = useFeatureFlag('pdf-controls')
return (
<>
<div className="toolbar-pdf-left">
@ -62,6 +65,9 @@ function PdfPreviewHybridToolbarInner() {
<PdfHybridDownloadButton />
</div>
<div className="toolbar-pdf-right">
{hasNewPdfToolbar && (
<div className="toolbar-pdf-controls" id="toolbar-pdf-controls" />
)}
<PdfHybridCodeCheckButton />
<SwitchToEditorButton />
<DetachedSynctexControl />

View file

@ -0,0 +1,45 @@
import Button from 'react-bootstrap/lib/Button'
import MaterialIcon from '@/shared/components/material-icon'
import Tooltip from '@/shared/components/tooltip'
type PDFToolbarButtonProps = {
tooltipId: string
icon: string
label: string
onClick: () => void
shortcut?: string
disabled?: boolean
}
export default function PDFToolbarButton({
tooltipId,
disabled,
label,
icon,
onClick,
shortcut,
}: PDFToolbarButtonProps) {
return (
<Tooltip
id={tooltipId}
description={
<>
<div>{label}</div>
{shortcut && <div>{shortcut}</div>}
</>
}
overlayProps={{ placement: 'bottom' }}
>
<Button
aria-label={label}
bsSize="large"
bsStyle={null}
className="pdfjs-toolbar-button"
disabled={disabled}
onClick={onClick}
>
<MaterialIcon type={icon} />
</Button>
</Tooltip>
)
}

View file

@ -0,0 +1,76 @@
import { useRef } from 'react'
import PdfPageNumberControl from './pdf-page-number-control'
import PdfZoomButtons from './pdf-zoom-buttons'
import { Button, Overlay, Popover } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import MaterialIcon from '@/shared/components/material-icon'
import Tooltip from '@/shared/components/tooltip'
import useDropdown from '@/shared/hooks/use-dropdown'
type PdfViewerControlsMenuButtonProps = {
setZoom: (zoom: string) => void
setPage: (page: number) => void
page: number
totalPages: number
}
export default function PdfViewerControlsMenuButton({
setZoom,
setPage,
page,
totalPages,
}: PdfViewerControlsMenuButtonProps) {
const { t } = useTranslation()
const {
open: popoverOpen,
onToggle: togglePopover,
ref: popoverRef,
} = useDropdown()
const targetRef = useRef<any>(null)
return (
<>
<Tooltip
id="pdf-controls-menu-tooltip"
description={t('view_options')}
overlayProps={{ placement: 'bottom' }}
>
<Button
className="pdfjs-toolbar-popover-button"
onClick={togglePopover}
ref={targetRef}
>
<MaterialIcon type="more_horiz" />
</Button>
</Tooltip>
<Overlay
show={popoverOpen}
target={targetRef.current}
placement="bottom"
containerPadding={0}
animation
rootClose
onHide={() => togglePopover(false)}
>
<Popover
className="pdfjs-toolbar-popover"
id="pdf-toolbar-popover-menu"
ref={popoverRef}
>
<PdfPageNumberControl
setPage={setPage}
page={page}
totalPages={totalPages}
/>
<div className="pdfjs-zoom-controls">
<PdfZoomButtons setZoom={setZoom} />
</div>
</Popover>
</Overlay>
</>
)
}

View file

@ -0,0 +1,112 @@
import { memo, useCallback, useState } from 'react'
import { createPortal } from 'react-dom'
import PdfPageNumberControl from './pdf-page-number-control'
import PdfZoomButtons from './pdf-zoom-buttons'
import PdfZoomDropdown from './pdf-zoom-dropdown'
import { useResizeObserver } from '@/shared/hooks/use-resize-observer'
import PdfViewerControlsMenuButton from './pdf-viewer-controls-menu-button'
type PdfViewerControlsToolbarProps = {
setZoom: (zoom: string) => void
rawScale: number
setPage: (page: number) => void
page: number
totalPages: number
}
function PdfViewerControlsToolbar({
setZoom,
rawScale,
setPage,
page,
totalPages,
}: PdfViewerControlsToolbarProps) {
const toolbarControlsElement = document.querySelector('#toolbar-pdf-controls')
const [availableWidth, setAvailableWidth] = useState<number>(1000)
const handleResize = useCallback(
element => {
setAvailableWidth(element.offsetWidth)
},
[setAvailableWidth]
)
const { elementRef: pdfControlsRef } = useResizeObserver(handleResize)
if (!toolbarControlsElement) {
return null
}
const InnerControlsComponent =
availableWidth >= 300
? PdfViewerControlsToolbarFull
: PdfViewerControlsToolbarSmall
return createPortal(
<div className="pdfjs-viewer-controls" ref={pdfControlsRef}>
<InnerControlsComponent
setZoom={setZoom}
rawScale={rawScale}
setPage={setPage}
page={page}
totalPages={totalPages}
/>
</div>,
toolbarControlsElement
)
}
type InnerControlsProps = {
setZoom: (zoom: string) => void
rawScale: number
setPage: (page: number) => void
page: number
totalPages: number
}
function PdfViewerControlsToolbarFull({
setZoom,
rawScale,
setPage,
page,
totalPages,
}: InnerControlsProps) {
return (
<>
<PdfPageNumberControl
setPage={setPage}
page={page}
totalPages={totalPages}
/>
<div className="pdfjs-zoom-controls">
<PdfZoomButtons setZoom={setZoom} />
<PdfZoomDropdown rawScale={rawScale} setZoom={setZoom} />
</div>
</>
)
}
function PdfViewerControlsToolbarSmall({
setZoom,
rawScale,
setPage,
page,
totalPages,
}: InnerControlsProps) {
return (
<div className="pdfjs-viewer-controls-small">
<PdfZoomDropdown rawScale={rawScale} setZoom={setZoom} />
<PdfViewerControlsMenuButton
setZoom={setZoom}
setPage={setPage}
page={page}
totalPages={totalPages}
/>
</div>
)
}
export default memo(PdfViewerControlsToolbar)

View file

@ -0,0 +1,37 @@
import { ButtonGroup } from 'react-bootstrap'
import PDFToolbarButton from './pdf-toolbar-button'
import { useTranslation } from 'react-i18next'
const isMac = /Mac/.test(window.navigator?.platform)
type PdfZoomButtonsProps = {
setZoom: (zoom: string) => void
}
function PdfZoomButtons({ setZoom }: PdfZoomButtonsProps) {
const { t } = useTranslation()
const zoomInShortcut = isMac ? '⌘+' : 'Ctrl++'
const zoomOutShortcut = isMac ? '⌘-' : 'Ctrl+-'
return (
<ButtonGroup className="pdfjs-toolbar-buttons">
<PDFToolbarButton
tooltipId="pdf-controls-zoom-out-tooltip"
label={t('zoom_out')}
icon="remove"
onClick={() => setZoom('zoom-out')}
shortcut={zoomOutShortcut}
/>
<PDFToolbarButton
tooltipId="pdf-controls-zoom-in-tooltip"
label={t('zoom_in')}
icon="add"
onClick={() => setZoom('zoom-in')}
shortcut={zoomInShortcut}
/>
</ButtonGroup>
)
}
export default PdfZoomButtons

View file

@ -0,0 +1,133 @@
import { Dropdown, MenuItem } from 'react-bootstrap'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ControlledDropdown from '@/shared/components/controlled-dropdown'
import classNames from 'classnames'
const isMac = /Mac/.test(window.navigator?.platform)
const shortcuts = isMac
? {
'zoom-in': ['⌘', '+'],
'zoom-out': ['⌘', '-'],
'fit-to-width': ['⌘', '0'],
}
: {
'zoom-in': ['Ctrl', '+'],
'zoom-out': ['Ctrl', '-'],
'fit-to-width': ['Ctrl', '0'],
}
type PdfZoomDropdownProps = {
setZoom: (zoom: string) => void
rawScale: number
}
const zoomValues = ['0.5', '0.75', '1', '1.5', '2', '4']
const rawScaleToPercentage = (rawScale: number) => {
return `${Math.round(rawScale * 100)}%`
}
function PdfZoomDropdown({ setZoom, rawScale }: PdfZoomDropdownProps) {
const { t } = useTranslation()
const [customZoomValue, setCustomZoomValue] = useState<string>(
rawScaleToPercentage(rawScale)
)
useEffect(() => {
setCustomZoomValue(rawScaleToPercentage(rawScale))
}, [rawScale])
return (
<ControlledDropdown
id="pdf-zoom-dropdown"
onSelect={eventKey => {
if (eventKey !== 'custom-zoom') setZoom(eventKey)
}}
pullRight
>
<Dropdown.Toggle
className="btn pdfjs-zoom-dropdown-button small"
value={rawScale}
title={rawScaleToPercentage(rawScale)}
/>
<Dropdown.Menu className="pdfjs-zoom-dropdown-menu">
<MenuItem
draggable={false}
disabled
className="pdfjs-custom-zoom-menu-item"
key="custom-zoom"
eventKey="custom-zoom"
>
<input
type="text"
onFocus={event => event.target.select()}
value={customZoomValue}
onKeyDown={event => {
if (event.key === 'Enter') {
const zoom = Number(customZoomValue.replace('%', '')) / 100
// Only allow zoom values between 10% and 999%
if (zoom < 0.1) {
setZoom('0.1')
} else if (zoom > 9.99) {
setZoom('9.99')
} else {
setZoom(`${zoom}`)
}
}
}}
onChange={event => {
const rawValue = event.target.value
const parsedValue = rawValue.replace(/[^0-9%]/g, '')
setCustomZoomValue(parsedValue)
}}
/>
</MenuItem>
<MenuItem divider />
<MenuItem draggable={false} key="zoom-in" eventKey="zoom-in">
<span>{t('zoom_in')}</span>
<Shortcut keys={shortcuts['zoom-in']} />
</MenuItem>
<MenuItem draggable={false} key="zoom-out" eventKey="zoom-out">
<span>{t('zoom_out')}</span>
<Shortcut keys={shortcuts['zoom-out']} />
</MenuItem>
<MenuItem draggable={false} key="page-width" eventKey="page-width">
{t('fit_to_width')}
<Shortcut keys={shortcuts['fit-to-width']} />
</MenuItem>
<MenuItem draggable={false} key="page-height" eventKey="page-height">
{t('fit_to_height')}
</MenuItem>
<MenuItem divider />
{zoomValues.map(value => (
<MenuItem draggable={false} key={value} eventKey={value}>
{rawScaleToPercentage(Number(value))}
</MenuItem>
))}
</Dropdown.Menu>
</ControlledDropdown>
)
}
function Shortcut({ keys }: { keys: string[] }) {
return (
<span className="pull-right">
{keys.map((key, idx) => (
<span
className={classNames({
'pdfjs-zoom-dropdown-mac-shortcut-char': key.length === 1,
})}
key={`${key}${idx}`}
>
{key}
</span>
))}
</span>
)
}
export default PdfZoomDropdown

View file

@ -17,15 +17,26 @@
.toolbar-pdf-orphan,
.toolbar-pdf-left,
.toolbar-pdf-right {
.toolbar-pdf-right,
.toolbar-pdf-controls {
display: flex;
align-items: center;
align-self: stretch;
}
.toolbar-pdf-orphan,
.toolbar-pdf-controls {
flex: 1 1 100%;
}
.toolbar-pdf-controls {
margin-right: 4px;
justify-content: flex-end;
}
.toolbar-pdf-right {
flex: 1 0 auto;
flex: 1;
justify-content: flex-end;
}
.toolbar-pdf-orphan {
@ -203,52 +214,163 @@
background-color: @link-color;
}
}
.pdfjs-controls {
position: absolute;
padding: @line-height-computed / 2;
top: 0;
left: 0;
display: inline-block;
z-index: 10; // above the PDF viewer
}
// TODO: remove this block once the new pdfjs toolbar is fully rolled out
.pdfjs-controls {
position: absolute;
padding: @line-height-computed / 2;
top: 0;
left: 0;
display: inline-block;
z-index: 10; // above the PDF viewer
.btn-group {
transition:
opacity 0.5s ease,
visibility 0 linear 0.5s;
visibility: hidden;
opacity: 0;
}
&:focus-within,
&:hover {
// make .pdfjs-controls and its children visible when it or any of its descendants are focused
.btn-group {
transition:
opacity 0.5s ease,
visibility 0 linear 0.5s;
visibility: hidden;
opacity: 0;
transition: none;
visibility: visible;
opacity: 1;
}
}
&:focus-within,
&:hover {
// make .pdfjs-controls and its children visible when it or any of its descendants are focused
.btn-group {
transition: none;
visibility: visible;
opacity: 1;
}
&:hover,
&.flash {
.btn-group {
transition: none;
visibility: visible;
opacity: 1;
}
}
&:hover,
&.flash {
.btn-group {
transition: none;
visibility: visible;
opacity: 1;
}
}
i.fa-arrows-h {
border-right: 2px solid @content-primary;
border-left: 2px solid @content-primary;
}
i.fa-arrows-v {
border-top: 2px solid @content-primary;
border-bottom: 2px solid @content-primary;
}
}
i.fa-arrows-h {
border-right: 2px solid @content-primary;
border-left: 2px solid @content-primary;
}
i.fa-arrows-v {
border-top: 2px solid @content-primary;
border-bottom: 2px solid @content-primary;
.pdfjs-viewer-controls {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
}
.pdfjs-zoom-controls {
border-left: 1px solid rgba(125, 125, 125, 0.3);
}
.pdfjs-toolbar-buttons {
display: flex;
gap: 8px;
margin-left: 8px;
margin-right: 8px;
}
.pdfjs-toolbar-button {
padding: 2px !important;
display: flex;
align-items: center;
&:hover {
color: @toolbar-btn-color;
}
}
.pdfjs-zoom-dropdown-button {
width: 60px;
text-align: right;
font-size: 14px;
font-weight: normal;
.caret {
margin-left: 4px;
}
}
.pdfjs-zoom-dropdown-mac-shortcut-char {
display: inline-block;
width: 1em;
text-align: center;
}
.pdfjs-custom-zoom-menu-item {
a:hover {
background-color: initial !important;
color: initial !important;
cursor: initial !important;
}
&.disabled {
a {
color: initial !important;
}
}
}
.pdfjs-page-number-input {
color: @toolbar-btn-color;
font-size: 14px;
padding: 8px 8px 8px 0;
display: flex;
align-items: center;
gap: 4px;
input {
color: initial;
border: 1px solid @neutral-60;
width: 32px;
height: 24px;
border-radius: @border-radius-base;
text-align: center;
}
}
.pdfjs-viewer-controls-small {
display: flex;
align-items: center;
gap: 8px;
}
.pdfjs-toolbar-popover-button {
padding: 2px !important;
display: flex;
align-items: center;
}
.pdfjs-toolbar-popover {
background-color: @editor-toolbar-bg;
border-radius: 4px;
.arrow {
display: none;
}
button {
background-color: transparent;
color: @toolbar-btn-color;
}
.popover-content {
display: flex;
align-items: center;
padding: 0;
}
}
// The new viewer UI has overflow on the inner element,
// so disable the overflow on the outer element
.pdf-viewer .pdfjs-viewer.pdfjs-viewer-outer {

View file

@ -1181,6 +1181,7 @@
"newsletter_info_unsubscribed": "You are currently <0>unsubscribed</0> to the __appName__ newsletter.",
"newsletter_onboarding_accept": "Id like emails about product offers and company news and events.",
"next": "Next",
"next_page": "Next page",
"next_payment_of_x_collectected_on_y": "The next payment of <0>__paymentAmmount__</0> will be collected on <1>__collectionDate__</1>.",
"nl": "Dutch",
"no": "Norwegian",
@ -1380,6 +1381,7 @@
"premium_plan_label": "Youre using <b>Overleaf Premium</b>",
"presentation": "Presentation",
"press_and_awards": "Press &amp; awards",
"previous_page": "Previous page",
"price": "Price",
"primarily_work_study_question": "Where do you primarily work or study?",
"primarily_work_study_question_company": "Company",
@ -2136,6 +2138,7 @@
"view_metrics_commons_subtext": "Monitor and download usage metrics for your Commons subscription",
"view_metrics_group_subtext": "Monitor and download usage metrics for your group subscription",
"view_more": "View more",
"view_options": "View options",
"view_pdf": "View PDF",
"view_source": "View Source",
"view_your_invoices": "View Your Invoices",