Просмотр исходного кода

Add PDF file preview (#15503)

GitOrigin-RevId: d5d84da65d328222556bfa9f5b585f7e47ba14ad
Alf Eaton 2 лет назад
Родитель
Сommit
d3e4f59885

+ 70 - 0
services/web/frontend/js/features/file-view/components/file-view-pdf.tsx

@@ -0,0 +1,70 @@
+import { FC, useCallback } from 'react'
+import useIsMounted from '@/shared/hooks/use-is-mounted'
+import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
+import { useIdeContext } from '@/shared/context/ide-context'
+import { debugConsole } from '@/utils/debugging'
+
+const FileViewPdf: FC<{
+  fileId: string
+  onLoad: () => void
+  onError: () => void
+}> = ({ fileId, onLoad, onError }) => {
+  const mountedRef = useIsMounted()
+
+  const { fileTreeManager } = useIdeContext()
+  const { previewByPath } = useFileTreePathContext()
+
+  const handleContainer = useCallback(
+    async (element: HTMLDivElement | null) => {
+      if (element) {
+        const { PDFJS } = await import(
+          '../../pdf-preview/util/pdf-js-versions'
+        ).then(m => m.default as any)
+
+        // bail out if loading PDF.js took too long
+        if (!mountedRef.current) {
+          onError()
+          return
+        }
+
+        const entity = fileTreeManager.findEntityById(fileId)
+        const path = fileTreeManager.getEntityPath(entity)
+        const preview = previewByPath(path)
+
+        if (!preview) {
+          onError()
+          return
+        }
+
+        const pdf = await PDFJS.getDocument(preview.url).promise
+
+        for (let i = 1; i <= pdf.numPages; i++) {
+          const page = await pdf.getPage(i)
+          const viewport = page.getViewport({ scale: 1 })
+
+          const canvas = document.createElement('canvas')
+          canvas.classList.add('pdf-page')
+          canvas.width = viewport.width
+          canvas.height = viewport.height
+          element.append(canvas)
+          page.render({
+            canvasContext: canvas.getContext('2d'),
+            viewport,
+          })
+        }
+
+        onLoad()
+
+        return () => {
+          pdf.cleanup().catch(debugConsole.error)
+          pdf.destroy()
+        }
+      }
+    },
+    [fileTreeManager, mountedRef, previewByPath, fileId, onLoad, onError]
+  )
+
+  return <div className="file-view-pdf" ref={handleContainer} />
+}
+
+export default FileViewPdf

+ 10 - 2
services/web/frontend/js/features/file-view/components/file-view.jsx

@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
 
 import FileViewHeader from './file-view-header'
 import FileViewImage from './file-view-image'
+import FileViewPdf from './file-view-pdf'
 import FileViewText from './file-view-text'
 import Icon from '../../../shared/components/icon'
 
@@ -24,8 +25,8 @@ export default function FileView({ file }) {
     editableFilenames.includes(file.name.toLowerCase())
 
   const isImageFile = imageExtensions.includes(extension)
-
-  const isUnpreviewableFile = !isEditableTextFile && !isImageFile
+  const isPdfFile = extension === 'pdf'
+  const isUnpreviewableFile = !isEditableTextFile && !isImageFile && !isPdfFile
 
   const handleLoad = useCallback(() => {
     setContentLoading(false)
@@ -52,6 +53,13 @@ export default function FileView({ file }) {
       {isEditableTextFile && (
         <FileViewText file={file} onLoad={handleLoad} onError={handleError} />
       )}
+      {isPdfFile && (
+        <FileViewPdf
+          fileId={file.id}
+          onLoad={handleLoad}
+          onError={handleError}
+        />
+      )}
     </>
   )
 

+ 12 - 1
services/web/frontend/stylesheets/app/editor/file-view.less

@@ -4,7 +4,8 @@
   background-color: @gray-lightest;
   text-align: center;
   overflow: auto;
-  img {
+  img,
+  .file-view-pdf {
     max-width: 100%;
     max-height: 90%;
     display: block;
@@ -14,6 +15,16 @@
     .box-shadow(0 2px 3px @gray;);
     background-color: white;
   }
+  .file-view-pdf {
+    overflow: auto;
+    width: max-content;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    .pdf-page:not(:last-of-type) {
+      border-bottom: 1px solid @gray;
+    }
+  }
   p.no-preview {
     margin-top: @line-height-computed / 2;
     font-size: 24px;