Enhance FilePreview Component with Download Progress Indication
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Integrated download progress tracking in the FilePreview component to provide users with real-time feedback during file loading. - Updated fetchFileContent function in ApiServerContext to support progress callbacks, enhancing user experience during file downloads. - Refactored rendering logic in FilePreview to display a progress overlay while files are being fetched, improving visual feedback and interaction.
This commit is contained in:
parent
49e23299da
commit
5f776f896f
@ -1,4 +1,5 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { Card, Flex } from 'antd'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
import {
|
||||
useCallback,
|
||||
@ -8,10 +9,10 @@ import {
|
||||
memo,
|
||||
useRef
|
||||
} from 'react'
|
||||
import LoadingPlaceholder from './LoadingPlaceholder'
|
||||
import GCodePreview from './GCodePreview'
|
||||
import ThreeDPreview from './ThreeDPreview'
|
||||
import PDFPreview from './PDFPreview'
|
||||
import ProgressDisplay from './ProgressDisplay'
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
|
||||
const hasExplicitPreviewHeight = (height) =>
|
||||
@ -25,6 +26,10 @@ const FilePreview = ({ file, style = {} }) => {
|
||||
const [fileObjectUrl, setFileObjectUrl] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [downloadProgress, setDownloadProgress] = useState({
|
||||
progress: 0,
|
||||
message: 'Loading file preview...'
|
||||
})
|
||||
|
||||
const currentId = useRef(null)
|
||||
|
||||
@ -39,78 +44,147 @@ const FilePreview = ({ file, style = {} }) => {
|
||||
if (error != null) {
|
||||
return
|
||||
}
|
||||
const requestId = file._id
|
||||
setLoading(true)
|
||||
const objectUrl = await fetchFileContent(file, false)
|
||||
setDownloadProgress({ progress: 0, message: 'Starting download...' })
|
||||
const objectUrl = await fetchFileContent(file, false, (progress) => {
|
||||
if (currentId.current !== requestId) {
|
||||
return
|
||||
}
|
||||
setDownloadProgress(progress)
|
||||
})
|
||||
if (currentId.current !== requestId) {
|
||||
return
|
||||
}
|
||||
if (objectUrl == null) {
|
||||
setLoading(false)
|
||||
setDownloadProgress(null)
|
||||
console.error('Failed to fetch file content', file)
|
||||
setError('Failed to fetch file content')
|
||||
return
|
||||
}
|
||||
setFileObjectUrl(objectUrl)
|
||||
setDownloadProgress(null)
|
||||
setLoading(false)
|
||||
}, [file, fetchFileContent, error])
|
||||
|
||||
useEffect(() => {
|
||||
if (file._id !== currentId.current) {
|
||||
setFileObjectUrl(null)
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
setDownloadProgress({
|
||||
progress: 0,
|
||||
message: 'Loading file preview...'
|
||||
})
|
||||
}
|
||||
if (file?.type && token != null && file._id !== currentId.current) {
|
||||
currentId.current = file._id
|
||||
fetchPreview()
|
||||
}
|
||||
}, [file._id, file?.type, fetchPreview, token])
|
||||
|
||||
if (isPdf) {
|
||||
if (error != null) {
|
||||
return <div style={{ color: 'red' }}>{error}</div>
|
||||
const showProgressOverlay = downloadProgress != null
|
||||
const wrapperHeight = hasExplicitPreviewHeight(style.height)
|
||||
? style.height
|
||||
: isPdf
|
||||
? '72vh'
|
||||
: style.height
|
||||
|
||||
const renderPreview = () => {
|
||||
if (isPdf) {
|
||||
return (
|
||||
<PDFPreview
|
||||
file={fileObjectUrl}
|
||||
loading={loading}
|
||||
style={{
|
||||
...style,
|
||||
height: '100%'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PDFPreview
|
||||
file={fileObjectUrl}
|
||||
loading={loading}
|
||||
style={{
|
||||
...style,
|
||||
height: hasExplicitPreviewHeight(style.height)
|
||||
? style.height
|
||||
: '72vh'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (loading == true || !file?.type) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (loading == true || !file?.type) {
|
||||
return <LoadingPlaceholder message={'Loading file preview...'} />
|
||||
if (isGcode && fileObjectUrl) {
|
||||
return (
|
||||
<GCodePreview
|
||||
src={fileObjectUrl}
|
||||
startLayer={0}
|
||||
endLayer={undefined}
|
||||
lineWidth={1}
|
||||
style={style}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (is3DModel && fileObjectUrl) {
|
||||
return (
|
||||
<ThreeDPreview
|
||||
src={fileObjectUrl}
|
||||
style={style}
|
||||
extension={file.extension}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (isImage && fileObjectUrl) {
|
||||
return <img src={fileObjectUrl} style={style}></img>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
return <div style={{ color: 'red' }}>{error}</div>
|
||||
}
|
||||
|
||||
if (isGcode && fileObjectUrl) {
|
||||
return (
|
||||
<GCodePreview
|
||||
src={fileObjectUrl}
|
||||
startLayer={0}
|
||||
endLayer={undefined}
|
||||
lineWidth={1}
|
||||
style={style}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (is3DModel && fileObjectUrl) {
|
||||
return (
|
||||
<ThreeDPreview
|
||||
src={fileObjectUrl}
|
||||
style={style}
|
||||
extension={file.extension}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (isImage && fileObjectUrl) {
|
||||
return <img src={fileObjectUrl} style={style}></img>
|
||||
}
|
||||
return null
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...style,
|
||||
position: 'relative',
|
||||
height: wrapperHeight,
|
||||
minHeight:
|
||||
showProgressOverlay && !wrapperHeight ? 240 : style.minHeight
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`previewProgressOverlay ${showProgressOverlay ? 'visible' : 'hidden'}`}
|
||||
>
|
||||
{showProgressOverlay ? (
|
||||
<Flex
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
align='center'
|
||||
justify='center'
|
||||
>
|
||||
<Card
|
||||
style={{
|
||||
maxWidth: 360,
|
||||
minWidth: 100,
|
||||
width: '100%',
|
||||
margin: 24
|
||||
}}
|
||||
styles={{ body: { padding: 18 } }}
|
||||
>
|
||||
<ProgressDisplay
|
||||
percent={Math.round(
|
||||
(Number(downloadProgress.progress) || 0) * 100
|
||||
)}
|
||||
status='active'
|
||||
>
|
||||
{downloadProgress.message || 'Downloading...'}
|
||||
</ProgressDisplay>
|
||||
</Card>
|
||||
</Flex>
|
||||
) : null}
|
||||
</div>
|
||||
{renderPreview()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
FilePreview.propTypes = {
|
||||
@ -122,6 +196,8 @@ FilePreview.propTypes = {
|
||||
const areEqual = (prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.file?._id === nextProps.file?._id &&
|
||||
prevProps.file?.type === nextProps.file?.type &&
|
||||
prevProps.file?.size === nextProps.file?.size &&
|
||||
JSON.stringify(prevProps.style) === JSON.stringify(nextProps.style)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1829,8 +1829,11 @@ const ApiServerProvider = ({ children }) => {
|
||||
}
|
||||
|
||||
// Download GCode file content
|
||||
const fetchFileContent = async (file, download = false) => {
|
||||
const fetchFileContent = async (file, download = false, onProgress = null) => {
|
||||
try {
|
||||
if (typeof onProgress === 'function') {
|
||||
onProgress({ progress: 0, message: 'Starting download...' })
|
||||
}
|
||||
const response = await axios.get(
|
||||
`${config.backendUrl}/files/${file._id}/content`,
|
||||
{
|
||||
@ -1838,13 +1841,32 @@ const ApiServerProvider = ({ children }) => {
|
||||
Accept: '*/*',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
responseType: 'blob'
|
||||
responseType: 'blob',
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
if (typeof onProgress !== 'function') {
|
||||
return
|
||||
}
|
||||
const total =
|
||||
progressEvent.total > 0
|
||||
? progressEvent.total
|
||||
: typeof file.size === 'number' && file.size > 0
|
||||
? file.size
|
||||
: 0
|
||||
const progress = total > 0 ? progressEvent.loaded / total : 0
|
||||
onProgress({
|
||||
progress: Math.min(progress, 1),
|
||||
message: 'Downloading...'
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
const blob = new Blob([response.data], {
|
||||
type: response.headers['content-type']
|
||||
})
|
||||
const fileURL = window.URL.createObjectURL(blob)
|
||||
if (typeof onProgress === 'function') {
|
||||
onProgress({ progress: 1, message: 'Download complete' })
|
||||
}
|
||||
if (download == true) {
|
||||
const fileLink = document.createElement('a')
|
||||
fileLink.href = fileURL
|
||||
@ -1858,7 +1880,7 @@ const ApiServerProvider = ({ children }) => {
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
showError(err, () => {
|
||||
fetchFileContent(file, download)
|
||||
fetchFileContent(file, download, onProgress)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user