Add quiz image zoom controls

This commit is contained in:
Daniel 2026-05-12 16:36:53 +02:00
parent 4f347a18a3
commit 17b0f06037
2 changed files with 64 additions and 7 deletions

View file

@ -230,20 +230,60 @@ body {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(15, 23, 42, 0.82);
cursor: zoom-out;
}
.image-lightbox-viewport {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: auto;
cursor: default;
}
.image-lightbox img {
max-width: min(100%, 1100px);
max-height: 92vh;
display: block;
border-radius: 10px;
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
cursor: default;
}
.image-lightbox-controls {
position: fixed;
top: 16px;
left: 50%;
z-index: 1001;
display: flex;
align-items: center;
gap: 8px;
padding: 7px 9px;
border: 1px solid rgba(255, 255, 255, 0.28);
border-radius: 999px;
background: rgba(15, 23, 42, 0.82);
color: white;
transform: translateX(-50%);
}
.image-lightbox-controls button {
min-width: 34px;
height: 30px;
padding: 0 10px;
border: 1px solid rgba(255, 255, 255, 0.28);
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
color: white;
cursor: pointer;
}
.image-lightbox-controls button:disabled {
cursor: not-allowed;
opacity: 0.45;
}
.image-lightbox-controls span {
min-width: 48px;
text-align: center;
font-size: 0.86rem;
font-weight: 700;
}
.image-lightbox-close {
position: fixed;
top: 16px;

View file

@ -398,6 +398,7 @@ export default function QuizPage() {
const [toast, setToast] = useState('')
const [navOpen, setNavOpen] = useState(false)
const [expandedImagePath, setExpandedImagePath] = useState('')
const [imageZoom, setImageZoom] = useState(1)
const [startedAt, setStartedAt] = useState(null)
const [favorites, setFavorites] = useState([])
const [activeReadSegment, setActiveReadSegment] = useState(null)
@ -1052,15 +1053,31 @@ const timerStarted = timeLeft !== null
{current.question_type === 'mcq' ? 'Multiple Choice' : current.question_type === 'true_false' ? 'True / False' : 'Fill in the Blank'}
</span>
{current.image_path && (
<button className="question-image-preview" onClick={() => setExpandedImagePath(current.image_path)} title="Expand image" type="button">
<button className="question-image-preview" onClick={() => { setImageZoom(1); setExpandedImagePath(current.image_path) }} title="Expand image" type="button">
<img src={`/uploads/${current.image_path}`} alt="Question illustration"
onError={e => e.currentTarget.closest('button').style.display = 'none'} />
</button>
)}
{expandedImagePath && (
<div className="image-lightbox" role="dialog" aria-modal="true" aria-label="Expanded question image" onClick={() => setExpandedImagePath('')}>
<div className="image-lightbox-controls" onClick={e => e.stopPropagation()}>
<button type="button" onClick={() => setImageZoom(z => Math.max(1, z - 0.25))} disabled={imageZoom <= 1}>-</button>
<span>{Math.round(imageZoom * 100)}%</span>
<button type="button" onClick={() => setImageZoom(z => Math.min(4, z + 0.25))} disabled={imageZoom >= 4}>+</button>
<button type="button" onClick={() => setImageZoom(1)} disabled={imageZoom === 1}>Reset</button>
</div>
<button className="image-lightbox-close" onClick={() => setExpandedImagePath('')} type="button" aria-label="Close expanded image">×</button>
<img src={`/uploads/${expandedImagePath}`} alt="Expanded question illustration" onClick={e => e.stopPropagation()} />
<div className="image-lightbox-viewport" onClick={e => e.stopPropagation()}>
<img
src={`/uploads/${expandedImagePath}`}
alt="Expanded question illustration"
style={{
maxWidth: imageZoom === 1 ? 'min(100%, 1100px)' : 'none',
maxHeight: imageZoom === 1 ? '92vh' : 'none',
width: imageZoom > 1 ? `${imageZoom * 100}%` : undefined,
}}
/>
</div>
</div>
)}
{(current.question_type === 'mcq' || current.question_type === 'true_false') && current.options ? (