From 5f776f896fee5b171f611f4f667eeba537a2b8a0 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 13 Sep 2026 23:21:15 +0100 Subject: [PATCH] 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. --- .../Dashboard/common/FilePreview.jsx | 168 +++++++++++++----- .../Dashboard/context/ApiServerContext.jsx | 28 ++- 2 files changed, 147 insertions(+), 49 deletions(-) diff --git a/src/components/Dashboard/common/FilePreview.jsx b/src/components/Dashboard/common/FilePreview.jsx index 78c2352f..39ca131d 100644 --- a/src/components/Dashboard/common/FilePreview.jsx +++ b/src/components/Dashboard/common/FilePreview.jsx @@ -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
{error}
+ const showProgressOverlay = downloadProgress != null + const wrapperHeight = hasExplicitPreviewHeight(style.height) + ? style.height + : isPdf + ? '72vh' + : style.height + + const renderPreview = () => { + if (isPdf) { + return ( + + ) } - return ( - - ) - } + if (loading == true || !file?.type) { + return null + } - if (loading == true || !file?.type) { - return + if (isGcode && fileObjectUrl) { + return ( + + ) + } + + if (is3DModel && fileObjectUrl) { + return ( + + ) + } + + if (isImage && fileObjectUrl) { + return + } + + return null } if (error != null) { return
{error}
} - if (isGcode && fileObjectUrl) { - return ( - - ) - } - - if (is3DModel && fileObjectUrl) { - return ( - - ) - } - - if (isImage && fileObjectUrl) { - return - } - return null + return ( +
+
+ {showProgressOverlay ? ( + + + + {downloadProgress.message || 'Downloading...'} + + + + ) : null} +
+ {renderPreview()} +
+ ) } 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) ) } diff --git a/src/components/Dashboard/context/ApiServerContext.jsx b/src/components/Dashboard/context/ApiServerContext.jsx index 1109eeb9..3da2e239 100644 --- a/src/components/Dashboard/context/ApiServerContext.jsx +++ b/src/components/Dashboard/context/ApiServerContext.jsx @@ -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) }) } }