feat: download and preview files from local backups automatically, if a local backup is available (#2076)
This commit is contained in:
@@ -7,4 +7,7 @@ export {
|
||||
FileBackupsMapping,
|
||||
FileBackupsDevice,
|
||||
FileBackupRecord,
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
FileDownloadProgress,
|
||||
} from '@standardnotes/snjs'
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { concatenateUint8Arrays } from '@/Utils'
|
||||
import { ApplicationEvent, FileItem } from '@standardnotes/snjs'
|
||||
import {
|
||||
ApplicationEvent,
|
||||
FileDownloadProgress,
|
||||
FileItem,
|
||||
fileProgressToHumanReadableString,
|
||||
} from '@standardnotes/snjs'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import Spinner from '@/Components/Spinner/Spinner'
|
||||
import FilePreviewError from './FilePreviewError'
|
||||
@@ -24,7 +29,7 @@ const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLe
|
||||
}, [file.mimeType])
|
||||
|
||||
const [isDownloading, setIsDownloading] = useState(true)
|
||||
const [downloadProgress, setDownloadProgress] = useState(0)
|
||||
const [downloadProgress, setDownloadProgress] = useState<FileDownloadProgress | undefined>()
|
||||
const [downloadedBytes, setDownloadedBytes] = useState<Uint8Array>()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -46,7 +51,7 @@ const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLe
|
||||
useEffect(() => {
|
||||
if (!isFilePreviewable || !isAuthorized) {
|
||||
setIsDownloading(false)
|
||||
setDownloadProgress(0)
|
||||
setDownloadProgress(undefined)
|
||||
setDownloadedBytes(undefined)
|
||||
return
|
||||
}
|
||||
@@ -60,15 +65,18 @@ const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLe
|
||||
|
||||
try {
|
||||
const chunks: Uint8Array[] = []
|
||||
setDownloadProgress(0)
|
||||
await application.files.downloadFile(file, async (decryptedChunk, progress) => {
|
||||
setDownloadProgress(undefined)
|
||||
const error = await application.files.downloadFile(file, async (decryptedChunk, progress) => {
|
||||
chunks.push(decryptedChunk)
|
||||
if (progress) {
|
||||
setDownloadProgress(Math.round(progress.percentComplete))
|
||||
setDownloadProgress(progress)
|
||||
}
|
||||
})
|
||||
const finalDecryptedBytes = concatenateUint8Arrays(chunks)
|
||||
setDownloadedBytes(finalDecryptedBytes)
|
||||
|
||||
if (!error) {
|
||||
const finalDecryptedBytes = concatenateUint8Arrays(chunks)
|
||||
setDownloadedBytes(finalDecryptedBytes)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
@@ -109,9 +117,17 @@ const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLe
|
||||
<div className="flex flex-grow flex-col items-center justify-center">
|
||||
<div className="flex items-center">
|
||||
<Spinner className="mr-3 h-5 w-5" />
|
||||
<div className="text-base font-semibold">{downloadProgress}%</div>
|
||||
{downloadProgress && (
|
||||
<div className="text-base font-semibold">{Math.floor(downloadProgress.percentComplete)}%</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="mt-3">Loading file...</span>
|
||||
{downloadProgress ? (
|
||||
<span className="mt-3">
|
||||
{fileProgressToHumanReadableString(downloadProgress, file.name, { showPercent: false })}
|
||||
</span>
|
||||
) : (
|
||||
<span className="mt-3">Loading...</span>
|
||||
)}
|
||||
</div>
|
||||
) : downloadedBytes ? (
|
||||
<PreviewComponent
|
||||
|
||||
@@ -73,10 +73,10 @@ const FileBackupsDesktop = ({ application, backupsService }: Props) => {
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
|
||||
{backupsEnabled && (
|
||||
<>
|
||||
<HorizontalSeparator classes="my-4" />
|
||||
|
||||
<PreferencesSegment>
|
||||
<>
|
||||
<Text className="mb-3">
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
FileDownloadProgress,
|
||||
fileProgressToHumanReadableString,
|
||||
OnChunkCallbackNoProgress,
|
||||
} from '@standardnotes/files'
|
||||
import { FilePreviewModalController } from './FilePreviewModalController'
|
||||
import {
|
||||
PopoverFileItemAction,
|
||||
@@ -260,6 +265,8 @@ export class FilesController extends AbstractViewController<FilesControllerEvent
|
||||
|
||||
const decryptedBytesArray: Uint8Array[] = []
|
||||
|
||||
let lastProgress: FileDownloadProgress | undefined
|
||||
|
||||
const result = await this.application.files.downloadFile(file, async (decryptedBytes, progress) => {
|
||||
if (isUsingStreamingSaver) {
|
||||
await saver.pushBytes(decryptedBytes)
|
||||
@@ -267,14 +274,14 @@ export class FilesController extends AbstractViewController<FilesControllerEvent
|
||||
decryptedBytesArray.push(decryptedBytes)
|
||||
}
|
||||
|
||||
if (progress) {
|
||||
const progressPercent = Math.floor(progress.percentComplete)
|
||||
const progressPercent = Math.floor(progress.percentComplete)
|
||||
|
||||
updateToast(downloadingToastId, {
|
||||
message: `Downloading file "${file.name}" (${progressPercent}%)`,
|
||||
progress: progressPercent,
|
||||
})
|
||||
}
|
||||
updateToast(downloadingToastId, {
|
||||
message: fileProgressToHumanReadableString(progress, file.name, { showPercent: true }),
|
||||
progress: progressPercent,
|
||||
})
|
||||
|
||||
lastProgress = progress
|
||||
})
|
||||
|
||||
if (result instanceof ClientDisplayableError) {
|
||||
@@ -293,7 +300,9 @@ export class FilesController extends AbstractViewController<FilesControllerEvent
|
||||
|
||||
addToast({
|
||||
type: ToastType.Success,
|
||||
message: 'Successfully downloaded file',
|
||||
message: `Successfully downloaded file${
|
||||
lastProgress && lastProgress.source === 'local' ? ' from local backup' : ''
|
||||
}`,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -364,13 +373,13 @@ export class FilesController extends AbstractViewController<FilesControllerEvent
|
||||
progress: initialProgress,
|
||||
})
|
||||
|
||||
const onChunk = async (chunk: Uint8Array, index: number, isLast: boolean) => {
|
||||
await this.application.files.pushBytesForUpload(operation, chunk, index, isLast)
|
||||
const onChunk: OnChunkCallbackNoProgress = async ({ data, index, isLast }) => {
|
||||
await this.application.files.pushBytesForUpload(operation, data, index, isLast)
|
||||
|
||||
const progress = Math.round(operation.getProgress().percentComplete)
|
||||
const percentComplete = Math.round(operation.getProgress().percentComplete)
|
||||
updateToast(toastId, {
|
||||
message: `Uploading file "${file.name}" (${progress}%)`,
|
||||
progress,
|
||||
message: `Uploading file "${file.name}" (${percentComplete}%)`,
|
||||
progress: percentComplete,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user