import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; import ReactSwipeableViews from 'react-swipeable-views'; import ExtendedVideoPlayer from 'soapbox/components/extended-video-player'; import Icon from 'soapbox/components/icon'; import IconButton from 'soapbox/components/icon-button'; import Audio from 'soapbox/features/audio'; import Video from 'soapbox/features/video'; import { useAppDispatch } from 'soapbox/hooks'; import ImageLoader from '../image-loader'; import type { List as ImmutableList } from 'immutable'; import type { Attachment, Status } from 'soapbox/types/entities'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); interface IMediaModal { media: ImmutableList, status?: Status, index: number, time?: number, onClose: () => void, } const MediaModal: React.FC = (props) => { const { media, status, onClose, time = 0, } = props; const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); const [index, setIndex] = useState(null); const [navigationHidden, setNavigationHidden] = useState(false); const handleSwipe = (index: number) => { setIndex(index % media.size); }; const handleNextClick = () => { setIndex((getIndex() + 1) % media.size); }; const handlePrevClick = () => { setIndex((media.size + getIndex() - 1) % media.size); }; const handleChangeIndex: React.MouseEventHandler = (e) => { const index = Number(e.currentTarget.getAttribute('data-index')); setIndex(index % media.size); }; const handleKeyDown = (e: KeyboardEvent) => { switch (e.key) { case 'ArrowLeft': handlePrevClick(); e.preventDefault(); e.stopPropagation(); break; case 'ArrowRight': handleNextClick(); e.preventDefault(); e.stopPropagation(); break; } }; useEffect(() => { window.addEventListener('keydown', handleKeyDown, false); return () => { window.removeEventListener('keydown', handleKeyDown); }; }, [index]); const getIndex = () => index !== null ? index : props.index; const toggleNavigation = () => { setNavigationHidden(!navigationHidden); }; const handleStatusClick: React.MouseEventHandler = e => { if (status && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); dispatch((_, getState) => { const account = typeof status.account === 'string' ? getState().accounts.get(status.account) : status.account; if (!account) return; history.push(`/@${account.acct}/posts/${status?.id}`); onClose(); }); } }; const handleCloserClick: React.MouseEventHandler = ({ target }) => { const whitelist = ['zoomable-image']; const activeSlide = document.querySelector('.media-modal .react-swipeable-view-container > div[aria-hidden="false"]'); const isClickOutside = target === activeSlide || !activeSlide?.contains(target as Element); const isWhitelisted = whitelist.some(w => (target as Element).classList.contains(w)); if (isClickOutside || isWhitelisted) { onClose(); } }; let pagination: React.ReactNode[] = []; const leftNav = media.size > 1 && ( ); const rightNav = media.size > 1 && ( ); if (media.size > 1) { pagination = media.toArray().map((item, i) => { const classes = ['media-modal__button']; if (i === getIndex()) { classes.push('media-modal__button--active'); } return (
  • ); }); } const isMultiMedia = media.map((image) => image.type !== 'image').toArray(); const content = media.map((attachment, i) => { const width = (attachment.meta.getIn(['original', 'width']) || undefined) as number | undefined; const height = (attachment.meta.getIn(['original', 'height']) || undefined) as number | undefined; const link = (status && ( )); if (attachment.type === 'image') { return ( ); } else if (attachment.type === 'video') { return (