From b97ce74ec2f827f2a1801f49bbc9e9d3dc9ca2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 01:56:43 +0100 Subject: [PATCH] pl-fe: try to fix media modal behavior after recent ports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../src/features/ui/components/modal-root.tsx | 1 - packages/pl-fe/src/modals/media-modal.tsx | 91 +++++++++++++------ .../pl-fe/src/styles/components/modal.scss | 20 ++++ 3 files changed, 84 insertions(+), 28 deletions(-) diff --git a/packages/pl-fe/src/features/ui/components/modal-root.tsx b/packages/pl-fe/src/features/ui/components/modal-root.tsx index bc92d1502..6fa0cd3c8 100644 --- a/packages/pl-fe/src/features/ui/components/modal-root.tsx +++ b/packages/pl-fe/src/features/ui/components/modal-root.tsx @@ -67,7 +67,6 @@ const ModalRoot: React.FC = () => { const index = modals.length - 1; const onClickClose = (type?: ModalType, all?: boolean) => { - console.log('Closing modal:', type, all); switch (type) { case 'COMPOSE': dispatch(cancelReplyCompose()); diff --git a/packages/pl-fe/src/modals/media-modal.tsx b/packages/pl-fe/src/modals/media-modal.tsx index 69f29970d..87e62a6de 100644 --- a/packages/pl-fe/src/modals/media-modal.tsx +++ b/packages/pl-fe/src/modals/media-modal.tsx @@ -1,8 +1,9 @@ +import { animated, useSpring } from '@react-spring/web'; import { Link } from '@tanstack/react-router'; +import { useDrag } from '@use-gesture/react'; import clsx from 'clsx'; -import React, { type RefCallback, useCallback, useEffect, useState } from 'react'; +import React, { type RefCallback, useCallback, useEffect, useMemo, useState } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import ReactSwipeableViews from 'react-swipeable-views'; import { fetchStatusWithContext } from 'pl-fe/actions/statuses'; import ExtendedVideoPlayer from 'pl-fe/components/extended-video-player'; @@ -25,6 +26,8 @@ import { makeGetStatus } from 'pl-fe/selectors'; import type { MediaAttachment } from 'pl-api'; import type { BaseModalProps } from 'pl-fe/features/ui/components/modal-root'; +const MIN_SWIPE_DISTANCE = 400; + const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, expand: { id: 'lightbox.expand', defaultMessage: 'Expand' }, @@ -36,19 +39,6 @@ const messages = defineMessages({ download: { id: 'video.download', defaultMessage: 'Download file' }, }); -// you can't use 100vh, because the viewport height is taller -// than the visible part of the document in some mobile -// browsers when its address bar is visible. -// https://developers.google.com/web/updates/2016/12/url-bar-resizing -const swipeableViewsStyle: React.CSSProperties = { - width: '100%', - height: '100%', -}; - -const containerStyle: React.CSSProperties = { - alignItems: 'center', // center vertically -}; - interface MediaModalProps { media?: Array; statusId?: string; @@ -77,6 +67,32 @@ const MediaModal: React.FC = (props) => { const [navigationHidden, setNavigationHidden] = useState(false); const [isFullScreen, setIsFullScreen] = useState(!status); + const [wrapperStyles, api] = useSpring(() => ({ + x: `-${index * 100}%`, + })); + + const handleChangeIndex = useCallback( + (newIndex: number, animate = false) => { + if (newIndex < 0) { + newIndex = media.length + newIndex; + } else if (newIndex >= media.length) { + newIndex = newIndex % media.length; + } + setIndex(newIndex); + setZoomedIn(false); + if (animate) { + void api.start({ x: `calc(-${newIndex * 100}% + 0px)` }); + } + }, + [api, media.length], + ); + const handlePrevClick = useCallback(() => { + handleChangeIndex(index - 1, true); + }, [handleChangeIndex, index]); + const handleNextClick = useCallback(() => { + handleChangeIndex(index + 1, true); + }, [handleChangeIndex, index]); + const [viewportDimensions, setViewportDimensions] = useState<{ width: number; height: number; @@ -93,10 +109,6 @@ const MediaModal: React.FC = (props) => { const hasMultipleImages = media.length > 1; - const handleSwipe = (index: number) => setIndex(index % media.length); - const handleNextClick = () => setIndex((index + 1) % media.length); - const handlePrevClick = () => setIndex((media.length + index - 1) % media.length); - const navigationHiddenClassName = navigationHidden ? 'pointer-events-none opacity-0' : ''; const handleKeyDown = (e: KeyboardEvent) => { @@ -114,6 +126,30 @@ const MediaModal: React.FC = (props) => { } }; + const bind = useDrag( + ({ active, movement: [mx], direction: [xDir], cancel }) => { + // Disable swipe when zoomed in. + if (zoomedIn) { + return; + } + + // If dragging and swipe distance is enough, change the index. + if ( + active && + Math.abs(mx) > Math.min(window.innerWidth / 4, MIN_SWIPE_DISTANCE) + ) { + handleChangeIndex(index - xDir); + cancel(); + } + // Set the x position via calc to ensure proper centering regardless of screen size. + const x = active ? mx : 0; + void api.start({ + x: `calc(-${index * 100}% + ${x}px)`, + }); + }, + { pointer: { capture: false } }, + ); + const handleDownload = () => { const mediaItem = hasMultipleImages ? media[index as number] : media[0]; window.open(mediaItem?.url); @@ -133,7 +169,7 @@ const MediaModal: React.FC = (props) => { setZoomedIn((prev) => !prev); }, []); - const content = media.map((attachment, i) => { + const content = useMemo(() => media.map((attachment, i) => { let width: number | undefined, height: number | undefined; if (attachment.type === 'image' || attachment.type === 'gifv' || attachment.type === 'video') { width = (attachment.meta?.original?.width); @@ -209,7 +245,7 @@ const MediaModal: React.FC = (props) => { } return null; - }); + }), [media.length, index, zoomedIn, handleZoomClick]); // Load data. useEffect(() => { @@ -313,6 +349,7 @@ const MediaModal: React.FC = (props) => { {/* Height based on height of top/bottom bars */}
{hasMultipleImages && ( @@ -328,14 +365,14 @@ const MediaModal: React.FC = (props) => {
)} - onClose()} > {content} - + {hasMultipleImages && (
diff --git a/packages/pl-fe/src/styles/components/modal.scss b/packages/pl-fe/src/styles/components/modal.scss index 1d43bfaaa..ed0d9a701 100644 --- a/packages/pl-fe/src/styles/components/modal.scss +++ b/packages/pl-fe/src/styles/components/modal.scss @@ -1,4 +1,6 @@ .media-modal { + touch-action: pan-y; + .audio-player.detailed, .extended-video-player { @apply flex items-center justify-center; @@ -15,5 +17,23 @@ @apply max-w-full max-h-[80%]; } } + + &__closer { + display: flex; + position: absolute; + top: 0; + inset-inline-start: 0; + inset-inline-end: 0; + bottom: 0; + + > div { + flex-shrink: 0; + overflow: auto; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + } + } }