Tom Butcher 5f776f896f
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
Enhance FilePreview Component with Download Progress Indication
- 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.
2026-09-13 23:21:15 +01:00

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)