From 878be33b7ca34be3a41b58d4b69cbad5ee919623 Mon Sep 17 00:00:00 2001 From: elpatron Date: Sat, 6 Jun 2026 20:40:13 +0200 Subject: [PATCH] feat: add fullscreen photo viewer overlay on click & resolve appearance compat warnings --- client/src/App.css | 74 ++++++++++++++++++++++++++ client/src/components/PhotoCapture.tsx | 58 ++++++++++++++++++-- 2 files changed, 129 insertions(+), 3 deletions(-) diff --git a/client/src/App.css b/client/src/App.css index c0d3ab4..25036a9 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -3184,6 +3184,7 @@ html.theme-cupertino .events-scroll-container { aspect-ratio: 16 / 9; background: #0b0c10; overflow: hidden; + cursor: pointer; } .photo-container img { @@ -3230,6 +3231,78 @@ html.theme-cupertino .events-scroll-container { white-space: nowrap; } +/* Photo Maximized Overlay */ +.photo-maximized-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(11, 12, 16, 0.9); + backdrop-filter: blur(15px); + -webkit-backdrop-filter: blur(15px); + display: flex; + align-items: center; + justify-content: center; + z-index: 11000; +} + +.photo-maximized-container { + position: relative; + max-width: 90vw; + max-height: 90vh; + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +} + +.photo-maximized-img { + max-width: 90vw; + max-height: 80vh; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.photo-maximized-close { + position: absolute; + top: -48px; + right: 0; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #f1f5f9; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; +} + +.photo-maximized-close:hover { + background: rgba(255, 255, 255, 0.2); + border-color: #ffffff; + transform: scale(1.08); +} + +.photo-maximized-caption { + font-size: 15px; + color: #f1f5f9; + background: rgba(15, 23, 42, 0.75); + padding: 8px 16px; + border-radius: 20px; + max-width: 80%; + text-align: center; + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + border: 1px solid rgba(255, 255, 255, 0.05); + word-break: break-word; +} + /* Custom Dialog Modals Styling */ .custom-dialog-overlay { position: fixed; @@ -4364,6 +4437,7 @@ html.theme-cupertino .events-scroll-container { .consumption-grid .input-group .input-text { flex-shrink: 0; -moz-appearance: textfield; + appearance: textfield; } .consumption-grid .input-text::-webkit-outer-spin-button, diff --git a/client/src/components/PhotoCapture.tsx b/client/src/components/PhotoCapture.tsx index bfba2af..db573e1 100644 --- a/client/src/components/PhotoCapture.tsx +++ b/client/src/components/PhotoCapture.tsx @@ -8,7 +8,7 @@ import { saveEntryPhoto, deleteEntryPhoto } from '../services/photoAttachments.j import { fileToCompressedJpegDataUrl } from '../utils/imageCompress.js' import { useLiveQuery } from 'dexie-react-hooks' import { useDialog } from './ModalDialog.tsx' -import { Camera, Image, Trash2 } from 'lucide-react' +import { Camera, Image, Trash2, X } from 'lucide-react' import { probeCameraAvailability } from '../utils/cameraAvailability.js' interface PhotoCaptureProps { @@ -33,10 +33,26 @@ export default function PhotoCapture({ entryId, logbookId, readOnly = false, pre const [error, setError] = useState(null) const [decryptedPhotos, setDecryptedPhotos] = useState([]) const [hasCamera, setHasCamera] = useState(false) + const [maximizedPhoto, setMaximizedPhoto] = useState(null) const fileInputRef = useRef(null) const cameraInputRef = useRef(null) + useEffect(() => { + if (!maximizedPhoto) return + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setMaximizedPhoto(null) + } + } + + window.addEventListener('keydown', handleKeyDown) + return () => { + window.removeEventListener('keydown', handleKeyDown) + } + }, [maximizedPhoto]) + useEffect(() => { let cancelled = false probeCameraAvailability().then((avail) => { @@ -246,14 +262,22 @@ export default function PhotoCapture({ entryId, logbookId, readOnly = false, pre ) : (
{decryptedPhotos.map((photo) => ( -
+
setMaximizedPhoto(photo)} + style={{ cursor: 'pointer' }} + >
{photo.caption {!readOnly && (
)} + + {maximizedPhoto && ( +
setMaximizedPhoto(null)} + > +
e.stopPropagation()}> + + {maximizedPhoto.caption + {maximizedPhoto.caption && ( +
+ {maximizedPhoto.caption} +
+ )} +
+
+ )}
) }