chore: make able to switch screenshots in zoom

This commit is contained in:
Alexander Medvedev 2026-04-11 11:10:50 +02:00
parent 0079da3b49
commit 5b99a76745
3 changed files with 449 additions and 374 deletions

40
package-lock.json generated
View File

@ -9,18 +9,18 @@
"version": "0.0.0",
"dependencies": {
"@marsidev/react-turnstile": "^1.5.0",
"@vnedyalk0v/react19-simple-maps": "^2.0.3",
"axios": "^1.14.0",
"@vnedyalk0v/react19-simple-maps": "^2.0.7",
"axios": "^1.15.0",
"d3-geo": "^3.1.1",
"d3-scale": "^4.0.2",
"i18next": "^26.0.3",
"i18next": "^26.0.4",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^1.7.0",
"lucide-react": "^1.8.0",
"or": "^0.2.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-i18next": "^17.0.2",
"react-is": "^19.2.4",
"react-is": "^19.2.5",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.14.0",
"recharts": "^3.8.1",
@ -28,7 +28,7 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^25.5.2",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
@ -37,8 +37,8 @@
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0",
"vite": "^8.0.4"
"typescript-eslint": "^8.58.1",
"vite": "^8.0.8"
}
},
"node_modules/@babel/code-frame": {
@ -1042,13 +1042,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.5.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz",
"integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==",
"version": "25.6.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
"undici-types": "~7.19.0"
}
},
"node_modules/@types/react": {
@ -1978,9 +1978,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.334",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz",
"integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==",
"version": "1.5.335",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz",
"integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==",
"dev": true,
"license": "ISC"
},
@ -4829,9 +4829,9 @@
}
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
"dev": true,
"license": "MIT"
},

View File

@ -12,18 +12,18 @@
},
"dependencies": {
"@marsidev/react-turnstile": "^1.5.0",
"@vnedyalk0v/react19-simple-maps": "^2.0.3",
"axios": "^1.14.0",
"@vnedyalk0v/react19-simple-maps": "^2.0.7",
"axios": "^1.15.0",
"d3-geo": "^3.1.1",
"d3-scale": "^4.0.2",
"i18next": "^26.0.3",
"i18next": "^26.0.4",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^1.7.0",
"lucide-react": "^1.8.0",
"or": "^0.2.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-i18next": "^17.0.2",
"react-is": "^19.2.4",
"react-is": "^19.2.5",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.14.0",
"recharts": "^3.8.1",
@ -31,7 +31,7 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^25.5.2",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
@ -40,10 +40,10 @@
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0",
"vite": "^8.0.4"
"typescript-eslint": "^8.58.1",
"vite": "^8.0.8"
},
"overrides": {
"vite": "^8.0.4"
"vite": "^8.0.8"
}
}

View File

@ -73,6 +73,7 @@ const PluginDetail = () => {
const [adShown, setAdShown] = useState(false);
const [mainScreenshot, setMainScreenshot] = useState<string | null>(null);
const [lightboxImage, setLightboxImage] = useState<string | null>(null);
const [lightboxIndex, setLightboxIndex] = useState<number>(0);
const [selectedLanguage] = useState('en');
const [currentDescription, setCurrentDescription] = useState('');
const [setLoading] = useState(true);
@ -141,6 +142,42 @@ const PluginDetail = () => {
}
}, [plugin, selectedLanguage]);
// ── Lightbox helpers ─────────────────────────────────────────────────────
const screenshots = plugin?.screenshots ?? [];
const openLightbox = (index: number) => {
setLightboxIndex(index);
setLightboxImage(screenshots[index]?.path ?? null);
};
const lightboxPrev = () => {
if (!screenshots.length) return;
const newIndex = (lightboxIndex - 1 + screenshots.length) % screenshots.length;
setLightboxIndex(newIndex);
setLightboxImage(screenshots[newIndex].path);
};
const lightboxNext = () => {
if (!screenshots.length) return;
const newIndex = (lightboxIndex + 1) % screenshots.length;
setLightboxIndex(newIndex);
setLightboxImage(screenshots[newIndex].path);
};
const closeLightbox = () => setLightboxImage(null);
// ── Keyboard navigation for lightbox ────────────────────────────────────
useEffect(() => {
if (!lightboxImage) return;
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'ArrowLeft') lightboxPrev();
else if (e.key === 'ArrowRight') lightboxNext();
else if (e.key === 'Escape') closeLightbox();
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [lightboxImage, lightboxIndex, screenshots]);
const hasReviewed = useMemo(() => {
if (!user || !plugin?.reviews || !Array.isArray(plugin.reviews)) return false;
return plugin.reviews.some((r: any) => r.username === user.username);
@ -357,7 +394,10 @@ const PluginDetail = () => {
alt="Main"
className="main-screenshot"
style={{ cursor: 'pointer' }}
onClick={() => setLightboxImage(mainScreenshot || plugin.screenshots[0].path)}
onClick={() => {
const idx = plugin.screenshots.findIndex((s: any) => s.path === (mainScreenshot || plugin.screenshots[0].path));
openLightbox(idx >= 0 ? idx : 0);
}}
/>
<div className="thumbnail-strip">
{plugin.screenshots.map((shot: any) => (
@ -549,7 +589,6 @@ const PluginDetail = () => {
<div className="stat-item"><strong>{plugin.views.toLocaleString()}</strong> Views</div>
<div className="stat-item">Category: <strong>{plugin.category}</strong></div>
{/* ADD THESE TWO */}
{plugin.version && (
<div className="stat-item">Version: <strong>{plugin.version}</strong></div>
)}
@ -616,12 +655,48 @@ const PluginDetail = () => {
{/* LIGHTBOX MODAL */}
{lightboxImage && (
<div className="lightbox-overlay" onClick={() => setLightboxImage(null)}>
<div className="lightbox-overlay" onClick={closeLightbox}>
<div className="lightbox-content" onClick={(e) => e.stopPropagation()}>
<img src={lightboxImage} alt="Expanded view" className="lightbox-img" />
<button className="lightbox-close" onClick={() => setLightboxImage(null)}>
<button className="lightbox-close" onClick={closeLightbox}>
&times;
</button>
{/* Left arrow — only shown when multiple screenshots exist */}
{screenshots.length > 1 && (
<button
className="lightbox-arrow lightbox-arrow-left"
onClick={(e) => { e.stopPropagation(); lightboxPrev(); }}
aria-label="Previous screenshot"
>
&#8249;
</button>
)}
{/* Right arrow */}
{screenshots.length > 1 && (
<button
className="lightbox-arrow lightbox-arrow-right"
onClick={(e) => { e.stopPropagation(); lightboxNext(); }}
aria-label="Next screenshot"
>
&#8250;
</button>
)}
{/* Dot indicators */}
{screenshots.length > 1 && (
<div className="lightbox-dots">
{screenshots.map((_: any, i: number) => (
<button
key={i}
className={`lightbox-dot ${i === lightboxIndex ? 'active' : ''}`}
onClick={(e) => { e.stopPropagation(); openLightbox(i); }}
aria-label={`Go to screenshot ${i + 1}`}
/>
))}
</div>
)}
</div>
</div>
)}