diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js
index 2e2c3cebf8..4f26f3e8ee 100644
--- a/services/web/app/src/Features/Project/ProjectController.js
+++ b/services/web/app/src/Features/Project/ProjectController.js
@@ -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',
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 34ab4a8c21..ccb705a800 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -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": "",
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.jsx
index 83e7a9a8e0..197051fa46 100644
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.jsx
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.jsx
@@ -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 (
-
-
+
+
-
+ {hasNewPdfToolbar ? (
+ toolbarInfoLoaded && (
+
+ )
+ ) : (
+
+ )}
)
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-page-number-control.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-page-number-control.tsx
new file mode 100644
index 0000000000..0770d84cc1
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-page-number-control.tsx
@@ -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 (
+ <>
+
+ setPage(page - 1)}
+ />
+ setPage(page + 1)}
+ />
+
+
+
+ / {totalPages}
+
+ >
+ )
+}
+
+export default PdfPageNumberControl
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx
index 06794c781c..390b0d608e 100644
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx
@@ -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 (
<>
@@ -62,6 +65,9 @@ function PdfPreviewHybridToolbarInner() {
+ {hasNewPdfToolbar && (
+
+ )}
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-toolbar-button.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-toolbar-button.tsx
new file mode 100644
index 0000000000..d263a05df2
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-toolbar-button.tsx
@@ -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 (
+
+ {label}
+ {shortcut && {shortcut}
}
+ >
+ }
+ overlayProps={{ placement: 'bottom' }}
+ >
+
+
+ )
+}
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-menu-button.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-menu-button.tsx
new file mode 100644
index 0000000000..488e26ee04
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-menu-button.tsx
@@ -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
(null)
+
+ return (
+ <>
+
+
+ togglePopover(false)}
+ >
+
+
+ >
+ )
+}
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx
new file mode 100644
index 0000000000..817eb497ce
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx
@@ -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(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(
+
+
+
,
+
+ 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 (
+ <>
+
+
+ >
+ )
+}
+
+function PdfViewerControlsToolbarSmall({
+ setZoom,
+ rawScale,
+ setPage,
+ page,
+ totalPages,
+}: InnerControlsProps) {
+ return (
+
+ )
+}
+
+export default memo(PdfViewerControlsToolbar)
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-zoom-buttons.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-zoom-buttons.tsx
new file mode 100644
index 0000000000..9e5b941829
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-zoom-buttons.tsx
@@ -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 (
+
+ setZoom('zoom-out')}
+ shortcut={zoomOutShortcut}
+ />
+ setZoom('zoom-in')}
+ shortcut={zoomInShortcut}
+ />
+
+ )
+}
+
+export default PdfZoomButtons
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-zoom-dropdown.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-zoom-dropdown.tsx
new file mode 100644
index 0000000000..f77a7c78d8
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-zoom-dropdown.tsx
@@ -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(
+ rawScaleToPercentage(rawScale)
+ )
+
+ useEffect(() => {
+ setCustomZoomValue(rawScaleToPercentage(rawScale))
+ }, [rawScale])
+
+ return (
+ {
+ if (eventKey !== 'custom-zoom') setZoom(eventKey)
+ }}
+ pullRight
+ >
+
+
+
+
+
+
+
+
+
+ {zoomValues.map(value => (
+
+ ))}
+
+
+ )
+}
+
+function Shortcut({ keys }: { keys: string[] }) {
+ return (
+
+ {keys.map((key, idx) => (
+
+ {key}
+
+ ))}
+
+ )
+}
+
+export default PdfZoomDropdown
diff --git a/services/web/frontend/stylesheets/app/editor/pdf.less b/services/web/frontend/stylesheets/app/editor/pdf.less
index 880a7a7027..0fb9f93960 100644
--- a/services/web/frontend/stylesheets/app/editor/pdf.less
+++ b/services/web/frontend/stylesheets/app/editor/pdf.less
@@ -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 {
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 0ea9597dc1..42b6466299 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1181,6 +1181,7 @@
"newsletter_info_unsubscribed": "You are currently <0>unsubscribed0> to the __appName__ newsletter.",
"newsletter_onboarding_accept": "I’d 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": "You’re using Overleaf Premium",
"presentation": "Presentation",
"press_and_awards": "Press & 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",