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 PropTypes from 'prop-types'
|
||||||
|
import { Card, Flex } from 'antd'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -8,10 +9,10 @@ import {
|
|||||||
memo,
|
memo,
|
||||||
useRef
|
useRef
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import LoadingPlaceholder from './LoadingPlaceholder'
|
|
||||||
import GCodePreview from './GCodePreview'
|
import GCodePreview from './GCodePreview'
|
||||||
import ThreeDPreview from './ThreeDPreview'
|
import ThreeDPreview from './ThreeDPreview'
|
||||||
import PDFPreview from './PDFPreview'
|
import PDFPreview from './PDFPreview'
|
||||||
|
import ProgressDisplay from './ProgressDisplay'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
|
|
||||||
const hasExplicitPreviewHeight = (height) =>
|
const hasExplicitPreviewHeight = (height) =>
|
||||||
@ -25,6 +26,10 @@ const FilePreview = ({ file, style = {} }) => {
|
|||||||
const [fileObjectUrl, setFileObjectUrl] = useState(null)
|
const [fileObjectUrl, setFileObjectUrl] = useState(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
|
const [downloadProgress, setDownloadProgress] = useState({
|
||||||
|
progress: 0,
|
||||||
|
message: 'Loading file preview...'
|
||||||
|
})
|
||||||
|
|
||||||
const currentId = useRef(null)
|
const currentId = useRef(null)
|
||||||
|
|
||||||
@ -39,78 +44,147 @@ const FilePreview = ({ file, style = {} }) => {
|
|||||||
if (error != null) {
|
if (error != null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const requestId = file._id
|
||||||
setLoading(true)
|
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) {
|
if (objectUrl == null) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
setDownloadProgress(null)
|
||||||
console.error('Failed to fetch file content', file)
|
console.error('Failed to fetch file content', file)
|
||||||
setError('Failed to fetch file content')
|
setError('Failed to fetch file content')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setFileObjectUrl(objectUrl)
|
setFileObjectUrl(objectUrl)
|
||||||
|
setDownloadProgress(null)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}, [file, fetchFileContent, error])
|
}, [file, fetchFileContent, error])
|
||||||
|
|
||||||
useEffect(() => {
|
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) {
|
if (file?.type && token != null && file._id !== currentId.current) {
|
||||||
currentId.current = file._id
|
currentId.current = file._id
|
||||||
fetchPreview()
|
fetchPreview()
|
||||||
}
|
}
|
||||||
}, [file._id, file?.type, fetchPreview, token])
|
}, [file._id, file?.type, fetchPreview, token])
|
||||||
|
|
||||||
if (isPdf) {
|
const showProgressOverlay = downloadProgress != null
|
||||||
if (error != null) {
|
const wrapperHeight = hasExplicitPreviewHeight(style.height)
|
||||||
return <div style={{ color: 'red' }}>{error}</div>
|
? style.height
|
||||||
|
: isPdf
|
||||||
|
? '72vh'
|
||||||
|
: style.height
|
||||||
|
|
||||||
|
const renderPreview = () => {
|
||||||
|
if (isPdf) {
|
||||||
|
return (
|
||||||
|
<PDFPreview
|
||||||
|
file={fileObjectUrl}
|
||||||
|
loading={loading}
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
height: '100%'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (loading == true || !file?.type) {
|
||||||
<PDFPreview
|
return null
|
||||||
file={fileObjectUrl}
|
}
|
||||||
loading={loading}
|
|
||||||
style={{
|
|
||||||
...style,
|
|
||||||
height: hasExplicitPreviewHeight(style.height)
|
|
||||||
? style.height
|
|
||||||
: '72vh'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading == true || !file?.type) {
|
if (isGcode && fileObjectUrl) {
|
||||||
return <LoadingPlaceholder message={'Loading file preview...'} />
|
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) {
|
if (error != null) {
|
||||||
return <div style={{ color: 'red' }}>{error}</div>
|
return <div style={{ color: 'red' }}>{error}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGcode && fileObjectUrl) {
|
return (
|
||||||
return (
|
<div
|
||||||
<GCodePreview
|
style={{
|
||||||
src={fileObjectUrl}
|
...style,
|
||||||
startLayer={0}
|
position: 'relative',
|
||||||
endLayer={undefined}
|
height: wrapperHeight,
|
||||||
lineWidth={1}
|
minHeight:
|
||||||
style={style}
|
showProgressOverlay && !wrapperHeight ? 240 : style.minHeight
|
||||||
/>
|
}}
|
||||||
)
|
>
|
||||||
}
|
<div
|
||||||
|
className={`previewProgressOverlay ${showProgressOverlay ? 'visible' : 'hidden'}`}
|
||||||
if (is3DModel && fileObjectUrl) {
|
>
|
||||||
return (
|
{showProgressOverlay ? (
|
||||||
<ThreeDPreview
|
<Flex
|
||||||
src={fileObjectUrl}
|
style={{ width: '100%', height: '100%' }}
|
||||||
style={style}
|
align='center'
|
||||||
extension={file.extension}
|
justify='center'
|
||||||
/>
|
>
|
||||||
)
|
<Card
|
||||||
}
|
style={{
|
||||||
|
maxWidth: 360,
|
||||||
if (isImage && fileObjectUrl) {
|
minWidth: 100,
|
||||||
return <img src={fileObjectUrl} style={style}></img>
|
width: '100%',
|
||||||
}
|
margin: 24
|
||||||
return null
|
}}
|
||||||
|
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 = {
|
FilePreview.propTypes = {
|
||||||
@ -122,6 +196,8 @@ FilePreview.propTypes = {
|
|||||||
const areEqual = (prevProps, nextProps) => {
|
const areEqual = (prevProps, nextProps) => {
|
||||||
return (
|
return (
|
||||||
prevProps.file?._id === nextProps.file?._id &&
|
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)
|
JSON.stringify(prevProps.style) === JSON.stringify(nextProps.style)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1829,8 +1829,11 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download GCode file content
|
// Download GCode file content
|
||||||
const fetchFileContent = async (file, download = false) => {
|
const fetchFileContent = async (file, download = false, onProgress = null) => {
|
||||||
try {
|
try {
|
||||||
|
if (typeof onProgress === 'function') {
|
||||||
|
onProgress({ progress: 0, message: 'Starting download...' })
|
||||||
|
}
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${config.backendUrl}/files/${file._id}/content`,
|
`${config.backendUrl}/files/${file._id}/content`,
|
||||||
{
|
{
|
||||||
@ -1838,13 +1841,32 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
Accept: '*/*',
|
Accept: '*/*',
|
||||||
Authorization: `Bearer ${token}`
|
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], {
|
const blob = new Blob([response.data], {
|
||||||
type: response.headers['content-type']
|
type: response.headers['content-type']
|
||||||
})
|
})
|
||||||
const fileURL = window.URL.createObjectURL(blob)
|
const fileURL = window.URL.createObjectURL(blob)
|
||||||
|
if (typeof onProgress === 'function') {
|
||||||
|
onProgress({ progress: 1, message: 'Download complete' })
|
||||||
|
}
|
||||||
if (download == true) {
|
if (download == true) {
|
||||||
const fileLink = document.createElement('a')
|
const fileLink = document.createElement('a')
|
||||||
fileLink.href = fileURL
|
fileLink.href = fileURL
|
||||||
@ -1858,7 +1880,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
showError(err, () => {
|
showError(err, () => {
|
||||||
fetchFileContent(file, download)
|
fetchFileContent(file, download, onProgress)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user