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.
206 lines
5.3 KiB
JavaScript
206 lines
5.3 KiB
JavaScript
import PropTypes from 'prop-types'
|
|
import { Card, Flex } from 'antd'
|
|
import { ApiServerContext } from '../context/ApiServerContext'
|
|
import {
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useState,
|
|
memo,
|
|
useRef
|
|
} from 'react'
|
|
import GCodePreview from './GCodePreview'
|
|
import ThreeDPreview from './ThreeDPreview'
|
|
import PDFPreview from './PDFPreview'
|
|
import ProgressDisplay from './ProgressDisplay'
|
|
import { AuthContext } from '../context/AuthContext'
|
|
|
|
const hasExplicitPreviewHeight = (height) =>
|
|
typeof height === 'number' ||
|
|
(typeof height === 'string' && height !== '100%' && height !== 'auto')
|
|
|
|
const FilePreview = ({ file, style = {} }) => {
|
|
const { token } = useContext(AuthContext)
|
|
const { fetchFileContent } = useContext(ApiServerContext)
|
|
|
|
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)
|
|
|
|
const extension = (file?.extension || '').toLowerCase()
|
|
const isGcode = ['.g', '.gcode'].includes(extension)
|
|
const is3DModel = ['.stl', '.3mf'].includes(extension)
|
|
const isImage = file?.type?.startsWith('image/') || false
|
|
const isPdf =
|
|
file?.type?.startsWith('application/pdf') || extension === '.pdf'
|
|
|
|
const fetchPreview = useCallback(async () => {
|
|
if (error != null) {
|
|
return
|
|
}
|
|
const requestId = file._id
|
|
setLoading(true)
|
|
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])
|
|
|
|
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%'
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (loading == true || !file?.type) {
|
|
return null
|
|
}
|
|
|
|
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>
|
|
}
|
|
|
|
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 = {
|
|
file: PropTypes.object.isRequired,
|
|
style: PropTypes.object
|
|
}
|
|
|
|
// Custom comparison function to only re-render when file._id changes
|
|
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)
|
|
)
|
|
}
|
|
|
|
export default memo(FilePreview, areEqual)
|