diff --git a/src/App.jsx b/src/App.jsx index 6314408..9b585d4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -52,6 +52,9 @@ import SvgAirDryerC from './pages/home/SvgAirDryerC'; import IndexHistoryAlarm from './pages/history/alarm/IndexHistoryAlarm'; import IndexHistoryEvent from './pages/history/event/IndexHistoryEvent'; +// Image Viewer +import ImageViewer from './Utils/ImageViewer'; + const App = () => { return ( @@ -76,6 +79,8 @@ const App = () => { } /> + } /> + }> } /> } /> @@ -148,7 +153,6 @@ const App = () => { } /> - {/* Catch-all */} } /> diff --git a/src/Utils/ImageViewer.jsx b/src/Utils/ImageViewer.jsx new file mode 100644 index 0000000..a463f32 --- /dev/null +++ b/src/Utils/ImageViewer.jsx @@ -0,0 +1,248 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { getFileUrl, getFolderFromFileType } from '../api/file-uploads'; + +const ImageViewer = () => { + const { fileName } = useParams(); + const [fileUrl, setFileUrl] = useState(''); + const [error, setError] = useState(''); + const [zoom, setZoom] = useState(1); + const [isImage, setIsImage] = useState(false); + + useEffect(() => { + if (!fileName) { + setError('No file specified'); + return; + } + + try { + const decodedFileName = decodeURIComponent(fileName); + const fileExtension = decodedFileName.split('.').pop()?.toLowerCase(); + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; + + setIsImage(imageExtensions.includes(fileExtension)); + + const folder = getFolderFromFileType(fileExtension); + + const url = getFileUrl(folder, decodedFileName); + setFileUrl(url); + + document.title = `File Viewer - ${decodedFileName}`; + } catch (error) { + + setError('Failed to load file'); + } + }, [fileName]); + + useEffect(() => { + const handleKeyDown = (e) => { + if (!isImage) return; + + if (e.key === '+' || e.key === '=') { + setZoom(prev => Math.min(prev + 0.1, 3)); + } else if (e.key === '-' || e.key === '_') { + setZoom(prev => Math.max(prev - 0.1, 0.1)); + } else if (e.key === '0') { + setZoom(1); + } else if (e.key === 'Escape') { + window.close(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [isImage]); + + + const handleWheel = (e) => { + if (!isImage || !e.ctrlKey) return; + + e.preventDefault(); + const delta = e.deltaY > 0 ? -0.1 : 0.1; + setZoom(prev => Math.min(Math.max(prev + delta, 0.1), 3)); + }; + + const handleZoomIn = () => setZoom(prev => Math.min(prev + 0.1, 3)); + const handleZoomOut = () => setZoom(prev => Math.max(prev - 0.1, 0.1)); + const handleResetZoom = () => setZoom(1); + + if (error) { + return ( +
+
+

Error

+

{error}

+
+
+ ); + } + + + if (!isImage) { + return ( +
+
+

File Type Not Supported

+

Image viewer only supports image files.

+

Please use direct file preview for PDFs and other documents.

+
+
+ ); + } + + return ( +
+ + {isImage && ( +
+ + + {Math.round(zoom * 100)}% + + + +
+ )} + + + {isImage && fileUrl ? ( +
+ {decodeURIComponent(fileName)} 1 ? 'move' : 'default' + }} + onError={() => setError('Failed to load image')} + draggable={false} + /> +
+ ) : isImage ? ( +
+

Loading image...

+
+ ) : null} + + + {isImage && ( +
+
Mouse wheel + Ctrl: Zoom
+
Keyboard: +/− Zoom, 0: Reset, ESC: Close
+
+ )} +
+ ); +}; + +export default ImageViewer; \ No newline at end of file