multiple file download with progress bar and percentage

This commit is contained in:
spencerwooo 2021-12-17 23:16:18 +08:00
parent 93376e071e
commit e20a2f52a5
No known key found for this signature in database
GPG key ID: 24CD550268849CA0
4 changed files with 61 additions and 27 deletions

View file

@ -14,10 +14,13 @@ import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon'
import { extensions, preview } from '../utils/getPreviewType'
import { useProtectedSWRInfinite } from '../utils/fetchWithSWR'
import { getBaseUrl } from '../utils/getBaseUrl'
import { downloadMultipleFiles, downloadTreelikeMultipleFiles, traverseFolder } from '../utils/downloadMultipleFiles'
import {
DownloadingToast,
downloadMultipleFiles,
downloadTreelikeMultipleFiles,
traverseFolder,
} from './MultiFileDownloader'
import { VideoPreview } from './previews/VideoPreview'
import { AudioPreview } from './previews/AudioPreview'
import Loading, { LoadingIcon } from './Loading'
import FourOhFour from './FourOhFour'
import Auth from './Auth'
@ -25,6 +28,8 @@ import TextPreview from './previews/TextPreview'
import MarkdownPreview from './previews/MarkdownPreview'
import CodePreview from './previews/CodePreview'
import OfficePreview from './previews/OfficePreview'
import AudioPreview from './previews/AudioPreview'
import VideoPreview from './previews/VideoPreview'
import DownloadBtn from './DownloadBtn'
// Disabling SSR for some previews (image gallery view, and PDF view)
@ -285,17 +290,16 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
el.remove()
} else if (files.length > 1) {
setTotalGenerating(true)
const toastId = toast.loading('Downloading selected files. Refresh to cancel, this may take some time...')
downloadMultipleFiles({ toastId, files, folder })
const toastId = toast.loading(DownloadingToast(router))
downloadMultipleFiles({ toastId, router, files, folder })
.then(() => {
setTotalGenerating(false)
toast.dismiss(toastId)
toast.success('Finished downloading selected files.')
toast.success('Finished downloading selected files.', { id: toastId })
})
.catch(() => {
setTotalGenerating(false)
toast.dismiss(toastId)
toast.error('Failed to download selected files.')
toast.error('Failed to download selected files.', { id: toastId })
})
}
}
@ -314,18 +318,16 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
})()
setFolderGenerating({ ...folderGenerating, [id]: true })
const toastId = toast.loading('Downloading folder. Refresh to cancel, this may take some time...')
const toastId = toast.loading(DownloadingToast(router))
downloadTreelikeMultipleFiles({ toastId, files, basePath: path, folder: name })
downloadTreelikeMultipleFiles({ toastId, router, files, basePath: path, folder: name })
.then(() => {
setFolderGenerating({ ...folderGenerating, [id]: false })
toast.dismiss(toastId)
toast.success('Finished downloading folder.')
toast.success('Finished downloading folder.', { id: toastId })
})
.catch(() => {
setFolderGenerating({ ...folderGenerating, [id]: false })
toast.dismiss(toastId)
toast.error('Failed to download folder.')
toast.error('Failed to download folder.', { id: toastId })
})
}

View file

@ -1,8 +1,37 @@
import { NextRouter } from 'next/router'
import toast from 'react-hot-toast'
import JSZip from 'jszip'
import { fetcher } from './fetchWithSWR'
import { getStoredToken } from './protectedRouteHandler'
import { fetcher } from '../utils/fetchWithSWR'
import { getStoredToken } from '../utils/protectedRouteHandler'
/**
* Generates a loading toast with file download progress support
* @param router Next router instance, used for reloading the page
* @param progress Current downloading and compression progress (returned by jszip metadata)
* @returns Toast component with loading progress
*/
export function DownloadingToast(router: NextRouter, progress?: string) {
return (
<div className="flex items-center space-x-2">
<div className="w-56">
<span>Downloading {progress ? `${progress}%` : 'selected files...'}</span>
<div className="relative mt-2">
<div className="overflow-hidden h-1 flex rounded bg-gray-100">
<div style={{ width: `${progress}%` }} className="text-white bg-gray-500 transition-all duration-100"></div>
</div>
</div>
</div>
<button
className="p-2 rounded bg-red-500 hover:bg-red-400 text-white focus:outline-none focus:ring focus:ring-red-300"
onClick={() => router.reload()}
>
Cancel
</button>
</div>
)
}
// Blob download helper
export function downloadBlob({ blob, name }: { blob: Blob; name: string }) {
@ -28,10 +57,12 @@ export function downloadBlob({ blob, name }: { blob: Blob; name: string }) {
*/
export async function downloadMultipleFiles({
toastId,
router,
files,
folder,
}: {
toastId: string
router: NextRouter
files: { name: string; url: string }[]
folder?: string
}): Promise<void> {
@ -50,9 +81,7 @@ export async function downloadMultipleFiles({
// Create zip file and download it
const b = await zip.generateAsync({ type: 'blob' }, metadata => {
toast.loading(`Downloading ${metadata.percent.toFixed(0)}%. Refresh to cancel...`, {
id: toastId,
})
toast.loading(DownloadingToast(router, metadata.percent.toFixed(0)), { id: toastId })
})
downloadBlob({ blob: b, name: folder ? folder + '.zip' : 'download.zip' })
}
@ -68,14 +97,15 @@ export async function downloadMultipleFiles({
* @param basePath Root dir path of files to be downloaded
* @param folder Optional folder name to hold files, otherwise flatten files in the zip
*/
export async function downloadTreelikeMultipleFiles({
toastId,
router,
files,
basePath,
folder,
}: {
toastId: string
router: NextRouter
files: AsyncGenerator<{
name: string
url?: string
@ -117,9 +147,7 @@ export async function downloadTreelikeMultipleFiles({
// Create zip file and download it
const b = await zip.generateAsync({ type: 'blob' }, metadata => {
toast.loading(`Downloading ${metadata.percent.toFixed(0)}%. Refresh to cancel...`, {
id: toastId,
})
toast.loading(DownloadingToast(router, metadata.percent.toFixed(0)), { id: toastId })
})
downloadBlob({ blob: b, name: folder ? folder + '.zip' : 'download.zip' })
}

View file

@ -11,7 +11,7 @@ enum PlayerState {
Paused,
}
export const AudioPreview: FunctionComponent<{ file: any }> = ({ file }) => {
const AudioPreview: FunctionComponent<{ file: any }> = ({ file }) => {
const [playerStatus, setPlayerStatus] = useState(PlayerState.Loading)
return (
@ -79,3 +79,5 @@ export const AudioPreview: FunctionComponent<{ file: any }> = ({ file }) => {
</>
)
}
export default AudioPreview

View file

@ -6,9 +6,9 @@ import { useClipboard } from 'use-clipboard-copy'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import toast, { Toaster } from 'react-hot-toast'
import { getBaseUrl } from "../../utils/getBaseUrl"
import { getBaseUrl } from '../../utils/getBaseUrl'
export const VideoPreview: FunctionComponent<{ file: any }> = ({ file }) => {
const VideoPreview: FunctionComponent<{ file: any }> = ({ file }) => {
const { asPath } = useRouter()
const clipboard = useClipboard()
@ -81,3 +81,5 @@ export const VideoPreview: FunctionComponent<{ file: any }> = ({ file }) => {
</>
)
}
export default VideoPreview