pdf-zoom-dropdown.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { useEffect, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import classNames from 'classnames'
  4. import {
  5. Dropdown,
  6. DropdownDivider,
  7. DropdownHeader,
  8. DropdownItem,
  9. DropdownMenu,
  10. DropdownToggle,
  11. } from '@/shared/components/dropdown/dropdown-menu'
  12. import FormControl from '@/shared/components/form/form-control'
  13. import { isMac } from '@/shared/utils/os'
  14. const shortcuts = isMac
  15. ? {
  16. 'zoom-in': ['⌘', '+'],
  17. 'zoom-out': ['⌘', '-'],
  18. 'fit-to-width': ['⌘', '0'],
  19. 'fit-to-height': ['⌘', '9'],
  20. }
  21. : {
  22. 'zoom-in': ['Ctrl', '+'],
  23. 'zoom-out': ['Ctrl', '-'],
  24. 'fit-to-width': ['Ctrl', '0'],
  25. 'fit-to-height': ['Ctrl', '9'],
  26. }
  27. type PdfZoomDropdownProps = {
  28. requestPresentationMode: () => void
  29. setZoom: (zoom: string) => void
  30. rawScale: number
  31. }
  32. const zoomValues = ['0.5', '0.75', '1', '1.5', '2', '4']
  33. const rawScaleToPercentage = (rawScale: number) => {
  34. return `${Math.round(rawScale * 100)}%`
  35. }
  36. function PdfZoomDropdown({
  37. requestPresentationMode,
  38. setZoom,
  39. rawScale,
  40. }: PdfZoomDropdownProps) {
  41. const { t } = useTranslation()
  42. const [customZoomValue, setCustomZoomValue] = useState<string>(
  43. rawScaleToPercentage(rawScale)
  44. )
  45. useEffect(() => {
  46. setCustomZoomValue(rawScaleToPercentage(rawScale))
  47. }, [rawScale])
  48. const showPresentOption = document.fullscreenEnabled
  49. return (
  50. <Dropdown
  51. onSelect={eventKey => {
  52. if (eventKey === 'custom-zoom') {
  53. return
  54. }
  55. if (eventKey === 'present') {
  56. requestPresentationMode()
  57. return
  58. }
  59. setZoom(eventKey)
  60. }}
  61. align="end"
  62. >
  63. <DropdownToggle
  64. id="pdf-zoom-dropdown"
  65. variant="link"
  66. className="pdf-toolbar-btn pdfjs-zoom-dropdown-button small"
  67. aria-label={t('pdf_zoom_level')}
  68. >
  69. {rawScaleToPercentage(rawScale)}
  70. </DropdownToggle>
  71. <DropdownMenu className="pdfjs-zoom-dropdown-menu">
  72. <li role="none">
  73. <DropdownItem
  74. disabled
  75. as="div"
  76. className="pdfjs-custom-zoom-menu-item"
  77. eventKey="custom-zoom"
  78. >
  79. <FormControl
  80. onFocus={event => event.target.select()}
  81. value={customZoomValue}
  82. onKeyDown={event => {
  83. if (event.key === 'Enter') {
  84. const zoom = Number(customZoomValue.replace('%', '')) / 100
  85. // Only allow zoom values between 10% and 999%
  86. if (zoom < 0.1) {
  87. setZoom('0.1')
  88. } else if (zoom > 9.99) {
  89. setZoom('9.99')
  90. } else {
  91. setZoom(`${zoom}`)
  92. }
  93. }
  94. }}
  95. onChange={event => {
  96. const rawValue = event.target.value
  97. const parsedValue = rawValue.replace(/[^0-9%]/g, '')
  98. setCustomZoomValue(parsedValue)
  99. }}
  100. />
  101. </DropdownItem>
  102. </li>
  103. <DropdownDivider />
  104. <li role="none">
  105. <DropdownItem
  106. as="button"
  107. eventKey="zoom-in"
  108. trailingIcon={<Shortcut keys={shortcuts['zoom-in']} />}
  109. >
  110. {t('zoom_in')}
  111. </DropdownItem>
  112. </li>
  113. <li role="none">
  114. <DropdownItem
  115. as="button"
  116. eventKey="zoom-out"
  117. trailingIcon={<Shortcut keys={shortcuts['zoom-out']} />}
  118. >
  119. {t('zoom_out')}
  120. </DropdownItem>
  121. </li>
  122. <li role="none">
  123. <DropdownItem
  124. as="button"
  125. eventKey="page-width"
  126. trailingIcon={<Shortcut keys={shortcuts['fit-to-width']} />}
  127. >
  128. {t('fit_to_width')}
  129. </DropdownItem>
  130. </li>
  131. <li role="none">
  132. <DropdownItem
  133. as="button"
  134. eventKey="page-height"
  135. trailingIcon={<Shortcut keys={shortcuts['fit-to-height']} />}
  136. >
  137. {t('fit_to_height')}
  138. </DropdownItem>
  139. </li>
  140. {showPresentOption && <DropdownDivider />}
  141. {showPresentOption && (
  142. <li role="none">
  143. <DropdownItem as="button" eventKey="present">
  144. {t('presentation_mode')}
  145. </DropdownItem>
  146. </li>
  147. )}
  148. <DropdownDivider />
  149. <DropdownHeader aria-hidden="true">{t('zoom_to')}</DropdownHeader>
  150. {zoomValues.map(value => (
  151. <li role="none" key={value}>
  152. <DropdownItem as="button" eventKey={value}>
  153. {rawScaleToPercentage(Number(value))}
  154. </DropdownItem>
  155. </li>
  156. ))}
  157. </DropdownMenu>
  158. </Dropdown>
  159. )
  160. }
  161. function Shortcut({ keys }: { keys: string[] }) {
  162. return (
  163. <span className="float-end">
  164. {keys.map((key, idx) => (
  165. <span
  166. className={classNames({
  167. 'pdfjs-zoom-dropdown-mac-shortcut-char': key.length === 1,
  168. })}
  169. key={`${key}${idx}`}
  170. >
  171. {key}
  172. </span>
  173. ))}
  174. </span>
  175. )
  176. }
  177. export default PdfZoomDropdown