122 lines
3.9 KiB
JavaScript
122 lines
3.9 KiB
JavaScript
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
import { useTranslation } from 'react-i18next';
|
|
import CloseIcon from "./icons/CloseIcon";
|
|
import Toast from "./Toast";
|
|
import { getImageUrl } from "../config/authConstants";
|
|
import { useContactSeller } from "../hooks/useContactSeller";
|
|
import { useAuthContext } from "../context/AuthContext";
|
|
|
|
export default function ImageViewer({ images, initialIndex = 0, onClose, postData }) {
|
|
const { t } = useTranslation();
|
|
const [index, setIndex] = useState(initialIndex);
|
|
const touchStart = useRef(null);
|
|
const swiped = useRef(false);
|
|
const { isAuthenticated } = useAuthContext();
|
|
const { contactSeller, error: contactError, clearError } = useContactSeller(postData);
|
|
|
|
const urls = useMemo(() => images.map(u => getImageUrl(u)), [images]);
|
|
|
|
const next = useCallback(() => {
|
|
setIndex(i => (i + 1) % urls.length);
|
|
}, [urls.length]);
|
|
|
|
const prev = useCallback(() => {
|
|
setIndex(i => (i - 1 + urls.length) % urls.length);
|
|
}, [urls.length]);
|
|
|
|
const handleImgClick = useCallback(() => {
|
|
next();
|
|
}, [next]);
|
|
|
|
useEffect(() => {
|
|
const handleKey = (e) => {
|
|
if (e.key === "Escape") onClose();
|
|
if (e.key === "ArrowRight") next();
|
|
if (e.key === "ArrowLeft") prev();
|
|
};
|
|
window.addEventListener("keydown", handleKey);
|
|
return () => window.removeEventListener("keydown", handleKey);
|
|
}, [onClose, next, prev]);
|
|
|
|
useEffect(() => {
|
|
setIndex(initialIndex);
|
|
}, [initialIndex]);
|
|
|
|
const handleTouchStart = useCallback((e) => {
|
|
touchStart.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
}, []);
|
|
|
|
const handleTouchEnd = useCallback((e) => {
|
|
if (!touchStart.current) return;
|
|
const dx = e.changedTouches[0].clientX - touchStart.current.x;
|
|
const dy = e.changedTouches[0].clientY - touchStart.current.y;
|
|
const isHorizontal = Math.abs(dx) > Math.abs(dy);
|
|
if (isHorizontal && Math.abs(dx) > 50) {
|
|
swiped.current = true;
|
|
if (dx > 0) prev();
|
|
else next();
|
|
setTimeout(() => { swiped.current = false; }, 400);
|
|
}
|
|
touchStart.current = null;
|
|
}, [prev, next]);
|
|
|
|
const handleContact = useCallback(() => {
|
|
// Закрываем вьюер перед переходом в чат
|
|
contactSeller(onClose);
|
|
}, [contactSeller, onClose]);
|
|
|
|
const showContact = isAuthenticated && postData && !postData.isOwner && postData.status !== 'CLOSED';
|
|
|
|
if (!urls.length) return null;
|
|
|
|
return (
|
|
<div
|
|
className="f-viewer-bg"
|
|
onTouchStart={handleTouchStart}
|
|
onTouchEnd={handleTouchEnd}
|
|
onClick={e => e.target === e.currentTarget && !swiped.current && onClose()}
|
|
>
|
|
<button type="button" className="f-viewer-close" onClick={onClose} aria-label={t('common.close')}>
|
|
<CloseIcon />
|
|
</button>
|
|
|
|
<div className="f-viewer">
|
|
<div className="f-viewer-img-wrap" onClick={handleImgClick}>
|
|
<img
|
|
src={urls[index]}
|
|
alt=""
|
|
className="f-viewer-img"
|
|
/>
|
|
</div>
|
|
|
|
{showContact && (
|
|
<button type="button" className="f-viewer-contact" onClick={handleContact}>
|
|
{t('postCard.write')}
|
|
</button>
|
|
)}
|
|
|
|
{urls.length > 1 && (
|
|
<div className="f-viewer-dots">
|
|
{urls.map((_, i) => (
|
|
<button
|
|
type="button"
|
|
key={i}
|
|
className={`f-viewer-dot${i === index ? " active" : ""}`}
|
|
onClick={() => setIndex(i)}
|
|
aria-label={t('viewer.goToImage', { number: i + 1 })}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{contactError && (
|
|
<Toast
|
|
message={contactError}
|
|
type="error"
|
|
onDismiss={clearError}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
} |