Merge branch 'fix-media-galerry-warning' into 'main'

Fix media gallery warning

See merge request soapbox-pub/soapbox!3208
This commit is contained in:
Alex Gleason 2024-11-03 15:29:07 +00:00
commit 069b6e504b
5 changed files with 60 additions and 57 deletions

View File

@ -17,11 +17,11 @@ const AttachmentThumbs = (props: IAttachmentThumbs) => {
const { media, onClick, sensitive } = props; const { media, onClick, sensitive } = props;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const fallback = <div className='media-gallery--compact' />; const fallback = <div className='!h-[50px] bg-transparent' />;
const onOpenMedia = (media: ImmutableList<Attachment>, index: number) => dispatch(openModal('MEDIA', { media, index })); const onOpenMedia = (media: ImmutableList<Attachment>, index: number) => dispatch(openModal('MEDIA', { media, index }));
return ( return (
<div className='attachment-thumbs'> <div className='relative'>
<Suspense fallback={fallback}> <Suspense fallback={fallback}>
<MediaGallery <MediaGallery
media={media} media={media}
@ -34,7 +34,11 @@ const AttachmentThumbs = (props: IAttachmentThumbs) => {
</Suspense> </Suspense>
{onClick && ( {onClick && (
<div className='attachment-thumbs__clickable-region' onClick={onClick} /> <button
className='absolute inset-0 size-full cursor-pointer'
onClick={onClick}
style={{ background: 'none', border: 'none', padding: 0 }}
/>
)} )}
</div> </div>
); );

View File

@ -2,7 +2,6 @@ import clsx from 'clsx';
import React, { useState, useRef, useLayoutEffect } from 'react'; import React, { useState, useRef, useLayoutEffect } from 'react';
import Blurhash from 'soapbox/components/blurhash'; import Blurhash from 'soapbox/components/blurhash';
import Icon from 'soapbox/components/icon';
import StillImage from 'soapbox/components/still-image'; import StillImage from 'soapbox/components/still-image';
import { MIMETYPE_ICONS } from 'soapbox/components/upload'; import { MIMETYPE_ICONS } from 'soapbox/components/upload';
import { useSettings, useSoapboxConfig } from 'soapbox/hooks'; import { useSettings, useSoapboxConfig } from 'soapbox/hooks';
@ -12,6 +11,8 @@ import { truncateFilename } from 'soapbox/utils/media';
import { isIOS } from '../is-mobile'; import { isIOS } from '../is-mobile';
import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../utils/media-aspect-ratio'; import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../utils/media-aspect-ratio';
import SvgIcon from './ui/icon/svg-icon';
import type { Property } from 'csstype'; import type { Property } from 'csstype';
import type { List as ImmutableList } from 'immutable'; import type { List as ImmutableList } from 'immutable';
@ -60,6 +61,7 @@ interface IItem {
dimensions: Dimensions; dimensions: Dimensions;
last?: boolean; last?: boolean;
total: number; total: number;
compact?: boolean;
} }
const Item: React.FC<IItem> = ({ const Item: React.FC<IItem> = ({
@ -71,6 +73,7 @@ const Item: React.FC<IItem> = ({
dimensions, dimensions,
last, last,
total, total,
compact,
}) => { }) => {
const { autoPlayGif } = useSettings(); const { autoPlayGif } = useSettings();
const { mediaPreview } = useSoapboxConfig(); const { mediaPreview } = useSoapboxConfig();
@ -111,16 +114,21 @@ const Item: React.FC<IItem> = ({
e.stopPropagation(); e.stopPropagation();
}; };
const handleVideoHover: React.MouseEventHandler<HTMLVideoElement> = ({ currentTarget: video }) => { const handleVideoHover = (event: React.SyntheticEvent<HTMLVideoElement>) => {
const video = event.currentTarget;
video.playbackRate = 3.0; video.playbackRate = 3.0;
video.play(); video.play();
}; };
const handleVideoLeave: React.MouseEventHandler<HTMLVideoElement> = ({ currentTarget: video }) => { const handleVideoLeave = (event: React.SyntheticEvent<HTMLVideoElement>) => {
const video = event.currentTarget;
video.pause(); video.pause();
video.currentTime = 0; video.currentTime = 0;
}; };
const handleFocus: React.FocusEventHandler<HTMLVideoElement> = handleVideoHover;
const handleBlur: React.FocusEventHandler<HTMLVideoElement> = handleVideoLeave;
let width: Dimensions['w'] = 100; let width: Dimensions['w'] = 100;
let height: Dimensions['h'] = '100%'; let height: Dimensions['h'] = '100%';
let top: Dimensions['t'] = 'auto'; let top: Dimensions['t'] = 'auto';
@ -144,43 +152,29 @@ const Item: React.FC<IItem> = ({
let thumbnail: React.ReactNode = ''; let thumbnail: React.ReactNode = '';
const ext = attachment.url.split('.').pop()?.toLowerCase(); const ext = attachment.url.split('.').pop()?.toLowerCase();
/*if (attachment.type === 'unknown' && ['gb', 'gbc'].includes(ext!)) { if (attachment.type === 'unknown') {
return (
<div
className={clsx('media-gallery__item', {
standalone,
'rounded-md': total > 1,
})}
key={attachment.id}
style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}
>
<Suspense fallback={<div className='media-gallery__item-thumbnail' />}>
<Gameboy className='media-gallery__item-thumbnail cursor-default' src={attachment.url} />
</Suspense>
</div>
);
} else */if (attachment.type === 'unknown') {
const filename = truncateFilename(attachment.url, MAX_FILENAME_LENGTH); const filename = truncateFilename(attachment.url, MAX_FILENAME_LENGTH);
const attachmentIcon = ( const attachmentIcon = (
<Icon <SvgIcon
className='size-16 text-gray-800 dark:text-gray-200' className={clsx('size-16 text-gray-800 dark:text-gray-200', { 'size-8': compact })}
src={MIMETYPE_ICONS[attachment.getIn(['pleroma', 'mime_type']) as string] || require('@tabler/icons/outline/paperclip.svg')} src={MIMETYPE_ICONS[attachment.getIn(['pleroma', 'mime_type']) as string] || require('@tabler/icons/outline/paperclip.svg')}
/> />
); );
return ( return (
<div <div
className={clsx('media-gallery__item', { className={clsx('relative float-left box-border block overflow-hidden rounded-sm border-0', {
standalone, standalone,
'rounded-md': total > 1, 'rounded-md': total > 1,
'!size-[50px] !inset-auto !float-left !mr-[50px]': compact,
})} })}
key={attachment.id} key={attachment.id}
style={{ position, float, left, top, right, bottom, height, width: `${width}%` }} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}
> >
<a className='media-gallery__item-thumbnail' href={attachment.url} target='_blank' style={{ cursor: 'pointer' }}> <a className='relative z-[1] block size-full cursor-zoom-in leading-none text-gray-400 no-underline' href={attachment.url} target='_blank' style={{ cursor: 'pointer' }}>
<Blurhash hash={attachment.blurhash} className='media-gallery__preview' /> <Blurhash hash={attachment.blurhash} className='absolute left-0 top-0 z-0 size-full rounded-lg bg-gray-200 object-cover dark:bg-gray-900' />
<span className='media-gallery__item__icons'>{attachmentIcon}</span> <span className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>{attachmentIcon}</span>
<span className='media-gallery__filename__label'>{filename}</span> <span className='pointer-events-none absolute bottom-1.5 left-1.5 z-[1] block bg-black/50 px-1.5 py-0.5 text-[11px] font-semibold leading-[18px] text-white opacity-90 transition-opacity duration-100 ease-linear'>{filename}</span>
</a> </a>
</div> </div>
); );
@ -189,7 +183,7 @@ const Item: React.FC<IItem> = ({
thumbnail = ( thumbnail = (
<a <a
className='media-gallery__item-thumbnail' className='relative z-[1] block size-full cursor-zoom-in leading-none text-gray-400 no-underline'
href={attachment.url} href={attachment.url}
onClick={handleClick} onClick={handleClick}
target='_blank' target='_blank'
@ -213,9 +207,9 @@ const Item: React.FC<IItem> = ({
} }
thumbnail = ( thumbnail = (
<div className={clsx('media-gallery__gifv', { autoplay: autoPlayGif })}> <div className='group relative size-full overflow-hidden'>
<video <video
className='media-gallery__item-gifv-thumbnail' className='relative top-0 z-10 size-full transform-none cursor-zoom-in rounded-md object-cover'
aria-label={attachment.description} aria-label={attachment.description}
title={attachment.description} title={attachment.description}
role='application' role='application'
@ -228,61 +222,65 @@ const Item: React.FC<IItem> = ({
{...conditionalAttributes} {...conditionalAttributes}
/> />
<span className='media-gallery__gifv__label'>GIF</span> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */} <span className={clsx('pointer-events-none absolute bottom-1.5 left-1.5 z-[1] block bg-black/50 px-1.5 py-0.5 text-[11px] font-semibold leading-[18px] text-white opacity-90 transition-opacity duration-100 ease-linear group-hover:opacity-100', { 'hidden': autoPlayGif })}>GIF</span> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */}
</div> </div>
); );
} else if (attachment.type === 'audio') { } else if (attachment.type === 'audio') {
thumbnail = ( thumbnail = (
<a <a
className={clsx('media-gallery__item-thumbnail')} className={clsx('relative z-[1] block size-full cursor-zoom-in leading-none text-gray-400 no-underline')}
href={attachment.url} href={attachment.url}
onClick={handleClick} onClick={handleClick}
target='_blank' target='_blank'
title={attachment.description} title={attachment.description}
> >
<span className='media-gallery__item__icons'><Icon src={require('@tabler/icons/outline/volume.svg')} /></span> <span className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'><SvgIcon className='size-24' src={require('@tabler/icons/outline/volume.svg')} /></span>
<span className='media-gallery__file-extension__label uppercase'>{ext}</span> <span className={clsx('pointer-events-none absolute bottom-1.5 left-1.5 z-[1] block bg-black/50 px-1.5 py-0.5 text-[11px] font-semibold uppercase leading-[18px] text-white opacity-90 transition-opacity duration-100 ease-linear', { 'hidden': compact })}>{ext}</span>
</a> </a>
); );
} else if (attachment.type === 'video') { } else if (attachment.type === 'video') {
thumbnail = ( thumbnail = (
<a <a
className={clsx('media-gallery__item-thumbnail')} className={clsx('relative z-[1] block size-full cursor-zoom-in leading-none text-gray-400 no-underline')}
href={attachment.url} href={attachment.url}
onClick={handleClick} onClick={handleClick}
target='_blank' target='_blank'
title={attachment.description} title={attachment.description}
> >
<video <video
className='size-full object-cover'
muted muted
loop loop
onMouseOver={handleVideoHover} onMouseOver={handleVideoHover}
onMouseOut={handleVideoLeave} onMouseOut={handleVideoLeave}
onFocus={handleFocus}
onBlur={handleBlur}
> >
<source src={attachment.url} /> <source src={attachment.url} />
</video> </video>
<span className='media-gallery__file-extension__label uppercase'>{ext}</span> <span className={clsx('pointer-events-none absolute bottom-1.5 left-1.5 z-[1] block bg-black/50 px-1.5 py-0.5 text-[11px] font-semibold uppercase leading-[18px] text-white opacity-90 transition-opacity duration-100 ease-linear', { 'hidden': compact })}>{ext}</span>
</a> </a>
); );
} }
return ( return (
<div <div
className={clsx('media-gallery__item', `media-gallery__item--${attachment.type}`, { className={clsx('relative float-left box-border block overflow-hidden rounded-sm border-0', {
standalone, standalone,
'rounded-md': total > 1, 'rounded-md': total > 1,
'!size-[50px] !inset-auto !float-left !mr-[50px]': compact,
})} })}
key={attachment.id} key={attachment.id}
style={{ position, float, left, top, right, bottom, height, width: `${width}%` }} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}
> >
{last && total > ATTACHMENT_LIMIT && ( {last && total > ATTACHMENT_LIMIT && (
<div className='media-gallery__item-overflow'> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */} <div className={clsx('pointer-events-none absolute inset-0 z-[2] flex size-full items-center justify-center bg-white/75 text-center text-[50px] font-bold text-gray-800', { '!text-5': compact })}> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */}
+{total - ATTACHMENT_LIMIT + 1} +{total - ATTACHMENT_LIMIT + 1}
</div> </div>
)} )}
<Blurhash <Blurhash
hash={attachment.blurhash} hash={attachment.blurhash}
className='media-gallery__preview' className='absolute left-0 top-0 z-0 size-full rounded-lg bg-gray-200 object-cover dark:bg-gray-900'
/> />
{visible && thumbnail} {visible && thumbnail}
</div> </div>
@ -561,6 +559,7 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
dimensions={sizeData.itemsDimensions[i]} dimensions={sizeData.itemsDimensions[i]}
last={i === ATTACHMENT_LIMIT - 1} last={i === ATTACHMENT_LIMIT - 1}
total={media.size} total={media.size}
compact={compact}
/> />
)); ));
@ -578,7 +577,7 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
return ( return (
<div <div
className={clsx(className, 'media-gallery', { 'media-gallery--compact': compact })} className={clsx(className, 'relative isolate box-border h-auto w-full overflow-hidden rounded-lg', { '!h-[50px] bg-transparent': compact })}
style={sizeData.style} style={sizeData.style}
ref={node} ref={node}
> >

View File

@ -166,7 +166,7 @@ const Upload: React.FC<IUpload> = ({
onDragEnter={onDragEnter} onDragEnter={onDragEnter}
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
> >
<Blurhash hash={media.blurhash} className='media-gallery__preview' /> <Blurhash hash={media.blurhash} className='absolute left-0 top-0 z-0 size-full rounded-lg bg-gray-200 object-cover dark:bg-gray-900' />
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}> <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
{({ scale }) => ( {({ scale }) => (
<div <div

View File

@ -2,8 +2,8 @@ import clsx from 'clsx';
import React, { useState } from 'react'; import React, { useState } from 'react';
import Blurhash from 'soapbox/components/blurhash'; import Blurhash from 'soapbox/components/blurhash';
import Icon from 'soapbox/components/icon';
import StillImage from 'soapbox/components/still-image'; import StillImage from 'soapbox/components/still-image';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import { useSettings } from 'soapbox/hooks'; import { useSettings } from 'soapbox/hooks';
import { isIOS } from 'soapbox/is-mobile'; import { isIOS } from 'soapbox/is-mobile';
@ -80,9 +80,9 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
conditionalAttributes.autoPlay = true; conditionalAttributes.autoPlay = true;
} }
thumbnail = ( thumbnail = (
<div className={clsx('media-gallery__gifv', { autoplay: autoPlayGif })}> <div className='group relative size-full overflow-hidden'>
<video <video
className='media-gallery__item-gifv-thumbnail' className='relative top-0 z-10 size-full transform-none cursor-zoom-in rounded-md object-cover'
aria-label={attachment.description} aria-label={attachment.description}
title={attachment.description} title={attachment.description}
role='application' role='application'
@ -94,7 +94,7 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
{...conditionalAttributes} {...conditionalAttributes}
/> />
<span className='media-gallery__gifv__label'>GIF</span> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */} <span className={clsx('pointer-events-none absolute bottom-1.5 left-1.5 z-[1] block bg-black/50 px-1.5 py-0.5 text-[11px] font-semibold leading-[18px] text-white opacity-90 transition-opacity duration-100 ease-linear group-hover:opacity-100', { 'hidden': autoPlayGif })}>GIF</span> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */}
</div> </div>
); );
} else if (attachment.type === 'audio') { } else if (attachment.type === 'audio') {
@ -102,28 +102,28 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
const fileExtensionLastIndex = remoteURL.lastIndexOf('.'); const fileExtensionLastIndex = remoteURL.lastIndexOf('.');
const fileExtension = remoteURL.slice(fileExtensionLastIndex + 1).toUpperCase(); const fileExtension = remoteURL.slice(fileExtensionLastIndex + 1).toUpperCase();
thumbnail = ( thumbnail = (
<div className='media-gallery__item-thumbnail'> <div className='relative z-[1] block size-full cursor-zoom-in leading-none text-gray-400 no-underline'>
<span className='media-gallery__item__icons'><Icon src={require('@tabler/icons/outline/volume.svg')} /></span> <span className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'><SvgIcon className='size-24' src={require('@tabler/icons/outline/volume.svg')} /></span>
<span className='media-gallery__file-extension__label'>{fileExtension}</span> <span className='pointer-events-none absolute bottom-1.5 left-1.5 z-[1] block bg-black/50 px-1.5 py-0.5 text-[11px] font-semibold leading-[18px] text-white opacity-90 transition-opacity duration-100 ease-linear'>{fileExtension}</span>
</div> </div>
); );
} }
if (!visible) { if (!visible) {
icon = ( icon = (
<span className='media-gallery__item__icons'> <span className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
<Icon src={require('@tabler/icons/outline/eye-off.svg')} /> <SvgIcon className='size-24' src={require('@tabler/icons/outline/eye-off.svg')} />
</span> </span>
); );
} }
return ( return (
<div className='col-span-1'> <div className='col-span-1'>
<a className='media-gallery__item-thumbnail aspect-1' href={status.url} target='_blank' onClick={handleClick} title={title}> <a className='relative z-[1] block aspect-1 size-full cursor-zoom-in leading-none text-gray-400 no-underline' href={status.url} target='_blank' onClick={handleClick} title={title}>
<Blurhash <Blurhash
hash={attachment.blurhash} hash={attachment.blurhash}
className={clsx('media-gallery__preview', { className={clsx('absolute left-0 top-0 z-0 size-full rounded-lg bg-gray-200 object-cover dark:bg-gray-900', {
'media-gallery__preview--hidden': visible, 'hidden': visible,
})} })}
/> />
{visible && thumbnail} {visible && thumbnail}

View File

@ -78,13 +78,13 @@ const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, de
const float = dimensions.float as any || 'left'; const float = dimensions.float as any || 'left';
const position = dimensions.pos as any || 'relative'; const position = dimensions.pos as any || 'relative';
return <div key={i} className='media-gallery__item animate-pulse bg-primary-200' style={{ position, float, left, top, right, bottom, height, width }} />; return <div key={i} className='relative float-left box-border block animate-pulse overflow-hidden rounded-sm border-0 bg-primary-200' style={{ position, float, left, top, right, bottom, height, width }} />;
}; };
const sizeData = getSizeData(media.size); const sizeData = getSizeData(media.size);
return ( return (
<div className='media-gallery media-gallery--placeholder' style={sizeData.get('style')} ref={handleRef}> <div className='relative isolate box-border h-auto w-full overflow-hidden rounded-lg' style={sizeData.get('style')} ref={handleRef}>
{media.take(4).map((_, i) => renderItem(sizeData.get('itemsDimensions')[i], i))} {media.take(4).map((_, i) => renderItem(sizeData.get('itemsDimensions')[i], i))}
</div> </div>
); );