From 05cdd28790701f98cabe88f137f04cfcc48e132d Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 29 May 2024 15:17:37 +0100 Subject: [PATCH] Merge pull request #18435 from overleaf/dp-ae-pdf-viewer-controls Update PDF viewer controls GitOrigin-RevId: 4e15b7cbd34e878d0175be635369b8d620188203 --- .../src/Features/Project/ProjectController.js | 1 + .../web/frontend/extracted-translations.json | 3 + .../pdf-preview/components/pdf-js-viewer.jsx | 81 +++++++- .../components/pdf-page-number-control.tsx | 76 +++++++ .../components/pdf-preview-hybrid-toolbar.jsx | 6 + .../components/pdf-toolbar-button.tsx | 45 ++++ .../pdf-viewer-controls-menu-button.tsx | 76 +++++++ .../pdf-viewer-controls-toolbar.tsx | 112 ++++++++++ .../components/pdf-zoom-buttons.tsx | 37 ++++ .../components/pdf-zoom-dropdown.tsx | 133 ++++++++++++ .../frontend/stylesheets/app/editor/pdf.less | 196 ++++++++++++++---- services/web/locales/en.json | 3 + 12 files changed, 721 insertions(+), 48 deletions(-) create mode 100644 services/web/frontend/js/features/pdf-preview/components/pdf-page-number-control.tsx create mode 100644 services/web/frontend/js/features/pdf-preview/components/pdf-toolbar-button.tsx create mode 100644 services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-menu-button.tsx create mode 100644 services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx create mode 100644 services/web/frontend/js/features/pdf-preview/components/pdf-zoom-buttons.tsx create mode 100644 services/web/frontend/js/features/pdf-preview/components/pdf-zoom-dropdown.tsx 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)} + /> + +
+
+ event.target.select()} + onBlur={handleSubmit} + onChange={event => { + const rawValue = event.target.value + setPageInputValue(rawValue.replace(/\D/g, '')) + }} + /> +
+ / {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 + > + + + + 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) + }} + /> + + + + {t('zoom_in')} + + + + {t('zoom_out')} + + + + {t('fit_to_width')} + + + + {t('fit_to_height')} + + + {zoomValues.map(value => ( + + {rawScaleToPercentage(Number(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>unsubscribed 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__ will be collected on <1>__collectionDate__.", "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",