pdf-preview.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import type { FC } from 'react'
  2. import { createPortal } from 'react-dom'
  3. import 'react-pdf-highlighter/dist/style.css'
  4. import { PdfHighlighter, PdfLoader } from 'react-pdf-highlighter'
  5. import { t } from 'i18next'
  6. import { RiCloseLine, RiZoomInLine, RiZoomOutLine } from '@remixicon/react'
  7. import React, { useState } from 'react'
  8. import { useHotkeys } from 'react-hotkeys-hook'
  9. import Loading from '@/app/components/base/loading'
  10. import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
  11. import Tooltip from '@/app/components/base/tooltip'
  12. type PdfPreviewProps = {
  13. url: string
  14. onCancel: () => void
  15. }
  16. const PdfPreview: FC<PdfPreviewProps> = ({
  17. url,
  18. onCancel,
  19. }) => {
  20. const media = useBreakpoints()
  21. const [scale, setScale] = useState(1)
  22. const [position, setPosition] = useState({ x: 0, y: 0 })
  23. const isMobile = media === MediaType.mobile
  24. const zoomIn = () => {
  25. setScale(prevScale => Math.min(prevScale * 1.2, 15))
  26. setPosition({ x: position.x - 50, y: position.y - 50 })
  27. }
  28. const zoomOut = () => {
  29. setScale((prevScale) => {
  30. const newScale = Math.max(prevScale / 1.2, 0.5)
  31. if (newScale === 1)
  32. setPosition({ x: 0, y: 0 })
  33. else
  34. setPosition({ x: position.x + 50, y: position.y + 50 })
  35. return newScale
  36. })
  37. }
  38. useHotkeys('esc', onCancel)
  39. useHotkeys('up', zoomIn)
  40. useHotkeys('down', zoomOut)
  41. return createPortal(
  42. <div
  43. className={`fixed inset-0 flex items-center justify-center bg-black/80 z-[1000] ${!isMobile && 'p-8'}`}
  44. onClick={e => e.stopPropagation()}
  45. tabIndex={-1}
  46. >
  47. <div
  48. className='h-[95vh] w-[100vw] max-w-full max-h-full overflow-hidden'
  49. style={{ transform: `scale(${scale})`, transformOrigin: 'center', scrollbarWidth: 'none', msOverflowStyle: 'none' }}
  50. >
  51. <PdfLoader
  52. workerSrc='/pdf.worker.min.mjs'
  53. url={url}
  54. beforeLoad={<div className='flex justify-center items-center h-64'><Loading type='app' /></div>}
  55. >
  56. {(pdfDocument) => {
  57. return (
  58. <PdfHighlighter
  59. pdfDocument={pdfDocument}
  60. enableAreaSelection={event => event.altKey}
  61. scrollRef={() => { }}
  62. onScrollChange={() => { }}
  63. onSelectionFinished={() => null}
  64. highlightTransform={() => { return <div/> }}
  65. highlights={[]}
  66. />
  67. )
  68. }}
  69. </PdfLoader>
  70. </div>
  71. <Tooltip popupContent={t('common.operation.zoomOut')}>
  72. <div className='absolute top-6 right-24 flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer'
  73. onClick={zoomOut}>
  74. <RiZoomOutLine className='w-4 h-4 text-gray-500'/>
  75. </div>
  76. </Tooltip>
  77. <Tooltip popupContent={t('common.operation.zoomIn')}>
  78. <div className='absolute top-6 right-16 flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer'
  79. onClick={zoomIn}>
  80. <RiZoomInLine className='w-4 h-4 text-gray-500'/>
  81. </div>
  82. </Tooltip>
  83. <Tooltip popupContent={t('common.operation.cancel')}>
  84. <div
  85. className='absolute top-6 right-6 flex items-center justify-center w-8 h-8 bg-white/8 rounded-lg backdrop-blur-[2px] cursor-pointer'
  86. onClick={onCancel}>
  87. <RiCloseLine className='w-4 h-4 text-gray-500'/>
  88. </div>
  89. </Tooltip>
  90. </div>,
  91. document.body,
  92. )
  93. }
  94. export default PdfPreview