add support for pdf previews with pdf.js

This commit is contained in:
spencerwooo
2021-06-25 00:42:46 +01:00
parent b1cced5e7e
commit a2f853259f
10 changed files with 1323 additions and 65 deletions
+3 -3
View File
@@ -8,12 +8,12 @@ const Breadcrumb: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =>
const { path } = query const { path } = query
if (Array.isArray(path)) { if (Array.isArray(path)) {
return ( return (
<div className="pb-4 text-sm text-gray-600 flex flex-wrap"> <div className="pb-4 text-sm text-gray-600 flex overflow-x-scroll">
<div className="p-1 hover:text-black transition-all duration-75"> <div className="p-1 hover:text-black transition-all duration-75 flex-shrink-0">
<Link href="/">🚩 Home</Link> <Link href="/">🚩 Home</Link>
</div> </div>
{path.map((q: string, i: number) => ( {path.map((q: string, i: number) => (
<div key={i} className="flex items-center"> <div key={i} className="flex items-center flex-shrink-0">
<div>/</div> <div>/</div>
<div className="p-1 hover:text-black transition-all duration-75"> <div className="p-1 hover:text-black transition-all duration-75">
<Link href={`/${path.slice(0, i + 1).join('/')}`}>{q}</Link> <Link href={`/${path.slice(0, i + 1).join('/')}`}>{q}</Link>
+12 -22
View File
@@ -2,20 +2,23 @@ import axios from 'axios'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { ParsedUrlQuery } from 'querystring' import { ParsedUrlQuery } from 'querystring'
import { FunctionComponent, useEffect, useState } from 'react' import { FunctionComponent, useState } from 'react'
import { ImageDecorator } from 'react-viewer/lib/ViewerProps' import { ImageDecorator } from 'react-viewer/lib/ViewerProps'
import useSWR from 'swr' import useSWR from 'swr'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import Loading from './Loading'
import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon' import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon'
import { extensions, preview } from '../utils/getPreviewType' import { extensions, preview } from '../utils/getPreviewType'
import { VideoPreview } from './previews/VideoPreview' import { VideoPreview } from './previews/VideoPreview'
import { AudioPreview } from './previews/AudioPreview' import { AudioPreview } from './previews/AudioPreview'
// View images as gallery // Disabling SSR for some previews (image gallery view, and PDF view)
const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false }) const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false })
const PDFPreview = dynamic(() => import('./previews/PDFPreview'), { ssr: false })
/** /**
* Convert raw bits file/folder size into a human readable string * Convert raw bits file/folder size into a human readable string
@@ -62,13 +65,13 @@ const FileListItem: FunctionComponent<{
</div> </div>
<div className="truncate">{c.name}</div> <div className="truncate">{c.name}</div>
</div> </div>
<div className="hidden md:visible font-mono text-sm col-span-2 text-gray-700"> <div className="invisible md:visible font-mono text-sm col-span-2 text-gray-700">
{new Date(c.lastModifiedDateTime).toLocaleString(undefined, { {new Date(c.lastModifiedDateTime).toLocaleString(undefined, {
dateStyle: 'short', dateStyle: 'short',
timeStyle: 'short', timeStyle: 'short',
})} })}
</div> </div>
<div className="hidden md:visible font-mono text-sm text-gray-700">{humanFileSize(c.size)}</div> <div className="invisible md:visible font-mono text-sm text-gray-700">{humanFileSize(c.size)}</div>
</div> </div>
) )
} }
@@ -89,21 +92,8 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
if (error) return <div>Failed to load</div> if (error) return <div>Failed to load</div>
if (!data) { if (!data) {
return ( return (
<div className="flex items-center justify-center bg-white shadow rounded py-32 space-x-1"> <div className="shadow bg-white rounded p-3">
<svg <Loading loadingText="Loading ..." />
className="animate-spin -ml-1 mr-3 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<div>Loading</div>
</div> </div>
) )
} }
@@ -140,8 +130,8 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
<div className="bg-white shadow rounded"> <div className="bg-white shadow rounded">
<div className="p-3 grid grid-cols-10 items-center space-x-2 border-b border-gray-200"> <div className="p-3 grid grid-cols-10 items-center space-x-2 border-b border-gray-200">
<div className="col-span-10 md:col-span-7 font-bold">Name</div> <div className="col-span-10 md:col-span-7 font-bold">Name</div>
<div className="hidden md:visible font-bold col-span-2">Last Modified</div> <div className="invisible md:visible font-bold col-span-2">Last Modified</div>
<div className="hidden md:visible font-bold">Size</div> <div className="invisible md:visible font-bold">Size</div>
</div> </div>
{imagesInFolder.length !== 0 && ( {imagesInFolder.length !== 0 && (
@@ -211,7 +201,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
return <AudioPreview file={resp} /> return <AudioPreview file={resp} />
case preview.pdf: case preview.pdf:
return <div>pdf</div> return <PDFPreview file={resp} />
default: default:
return <div className="bg-white shadow rounded">{fileName}</div> return <div className="bg-white shadow rounded">{fileName}</div>
+24
View File
@@ -0,0 +1,24 @@
import { FunctionComponent } from 'react'
const Loading: FunctionComponent<{ loadingText: string }> = ({ loadingText }) => {
return (
<div className="flex items-center justify-center rounded py-32 space-x-1">
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<div>{loadingText}</div>
</div>
)
}
export default Loading
+1 -1
View File
@@ -4,7 +4,7 @@ import siteConfig from '../config/site.json'
const Navbar = () => { const Navbar = () => {
return ( return (
<div className="text-left bg-white p-3 sticky top-0 bg-opacity-80 backdrop-blur-md shadow-sm"> <div className="text-left bg-white p-3 sticky top-0 bg-opacity-80 backdrop-blur-md shadow-sm z-20">
<div className="max-w-4xl w-full mx-auto flex items-center justify-between"> <div className="max-w-4xl w-full mx-auto flex items-center justify-between">
<h1 className="font-bold text-xl">{siteConfig.title}</h1> <h1 className="font-bold text-xl">{siteConfig.title}</h1>
<a href="https://github.com/spencerwooo/onedrive-vercel-index" target="_blank" rel="noopener noreferrer"> <a href="https://github.com/spencerwooo/onedrive-vercel-index" target="_blank" rel="noopener noreferrer">
+4 -4
View File
@@ -5,13 +5,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
export const AudioPreview: FunctionComponent<{ file: any }> = ({ file }) => { export const AudioPreview: FunctionComponent<{ file: any }> = ({ file }) => {
return ( return (
<div className="bg-white rounded shadow p-3 w-full"> <div className="bg-white rounded shadow p-3 w-full">
<div className="flex space-x-4"> <div className="flex flex-col space-y-4 md:flex-row md:space-x-4">
<div className="flex items-center justify-center p-10 bg-gray-100 rounded"> <div className="flex items-center justify-center bg-gray-100 rounded p-28 md:p-14">
<FontAwesomeIcon className="" icon="music" size="lg" /> <FontAwesomeIcon icon="music" size="lg" />
</div> </div>
<div className="flex flex-col w-full space-y-2"> <div className="flex flex-col w-full space-y-2">
<div>{file.name}</div> <div>{file.name}</div>
<div className="text-gray-500 text-sm"> <div className="text-gray-500 text-sm pb-4">
Last modified:{' '} Last modified:{' '}
{new Date(file.lastModifiedDateTime).toLocaleString(undefined, { {new Date(file.lastModifiedDateTime).toLocaleString(undefined, {
dateStyle: 'short', dateStyle: 'short',
+70
View File
@@ -0,0 +1,70 @@
import { FunctionComponent, useState } from 'react'
import { Document, Page, pdfjs } from 'react-pdf'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Loading from '../Loading'
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`
const PDFPreview: FunctionComponent<{ file: any }> = ({ file }) => {
const [pageNumber, setPageNumber] = useState(1)
const [totalPages, setTotalPages] = useState(0)
const [loadingText, setLoadingText] = useState('Loading PDF ...')
const onDocumentLoadSuccess = (pdf: any) => {
setTotalPages(pdf.numPages)
}
return (
<div className="bg-white rounded shadow md:p-3 w-full overflow-scroll" style={{ maxHeight: '90vh' }}>
<div className="w-full mx-auto border-2 md:shadow overflow-scroll" style={{ maxHeight: '60vh' }}>
<Document
file={file['@microsoft.graph.downloadUrl']}
onLoadSuccess={onDocumentLoadSuccess}
loading={<Loading loadingText={loadingText} />}
onLoadProgress={({ loaded, total }) => {
setLoadingText(`Loading PDF ${Math.round((loaded / total) * 100)}%`)
}}
>
<Page pageNumber={pageNumber} />
</Document>
</div>
<div className="flex space-x-2 my-4 md:mb-0 w-full items-center justify-center">
<button
className="px-3 py-1 bg-red-500 text-white rounded cursor-pointer focus:ring-2 focus:ring-red-500 focus:outline-none hover:bg-red-600 transition-all duration-75 disabled:opacity-50"
onClick={() => {
pageNumber > 1 && setPageNumber(pageNumber - 1)
}}
disabled={!(pageNumber > 1)}
>
<FontAwesomeIcon icon="arrow-left" />
</button>
<div className="px-3 py-1">
Page{' '}
<input
value={pageNumber}
className="w-10 mr-1 text-center p-1 bg-red-50 rounded focus:ring-2 focus:ring-red-500 focus:outline-none"
onChange={e => {
const v = parseInt(e.target.value)
if (v <= totalPages && v >= 0) {
setPageNumber(v)
}
}}
></input>
/<span className="ml-1 text-center">{totalPages}</span>
</div>
<button
className="px-3 py-1 bg-red-500 text-white rounded cursor-pointer focus:ring-2 focus:ring-red-500 focus:outline-none hover:bg-red-600 transition-all duration-75 disabled:opacity-50"
onClick={() => {
pageNumber < totalPages && setPageNumber(pageNumber + 1)
}}
disabled={!(pageNumber < totalPages)}
>
<FontAwesomeIcon icon="arrow-right" />
</button>
</div>
</div>
)
}
export default PDFPreview
+18
View File
@@ -1,4 +1,22 @@
module.exports = { module.exports = {
webpack: (config) => {
// load worker files as a urls with `file-loader`
config.module.rules.unshift({
test: /pdf\.worker\.(min\.)?js/,
use: [
{
loader: "file-loader",
options: {
name: "[contenthash].[ext]",
publicPath: "_next/static/worker",
outputPath: "static/worker"
}
}
]
});
return config;
},
reactStrictMode: true, reactStrictMode: true,
images: { images: {
domains: ['public.dm.files.1drv.com'], domains: ['public.dm.files.1drv.com'],
+1185 -33
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -18,12 +18,14 @@
"next": "11.0.0", "next": "11.0.0",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-pdf": "^5.3.0",
"react-player": "^2.9.0", "react-player": "^2.9.0",
"react-viewer": "^3.2.2", "react-viewer": "^3.2.2",
"swr": "^0.5.6" "swr": "^0.5.6"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "17.0.11", "@types/react": "17.0.11",
"@types/react-pdf": "^5.0.4",
"autoprefixer": "^10.2.6", "autoprefixer": "^10.2.6",
"eslint": "7.29.0", "eslint": "7.29.0",
"eslint-config-next": "11.0.0", "eslint-config-next": "11.0.0",
+4 -2
View File
@@ -14,7 +14,7 @@ import {
faFile, faFile,
faFolder, faFolder,
} from '@fortawesome/free-regular-svg-icons' } from '@fortawesome/free-regular-svg-icons'
import { faMusic } from '@fortawesome/free-solid-svg-icons' import { faMusic, faArrowLeft, faArrowRight } from '@fortawesome/free-solid-svg-icons'
import { faGithub, faMarkdown } from '@fortawesome/free-brands-svg-icons' import { faGithub, faMarkdown } from '@fortawesome/free-brands-svg-icons'
import type { AppProps } from 'next/app' import type { AppProps } from 'next/app'
@@ -34,7 +34,9 @@ library.add(
faFolder, faFolder,
faGithub, faGithub,
faMarkdown, faMarkdown,
faMusic faMusic,
faArrowLeft,
faArrowRight
) )
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {