From 1960aac90b16fce1ec620ac990aa931efcf04700 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Fri, 21 Mar 2025 11:23:49 +0100 Subject: [PATCH] Fix display of failed-to-load image attachments in web UI (#34217) --- .../mastodon/components/media_gallery.jsx | 8 +++++++- .../account_gallery/components/media_item.tsx | 13 ++++++++++++- app/javascript/styles/mastodon/components.scss | 4 ++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index 5132316600..e52e14b1ed 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -38,6 +38,7 @@ class Item extends PureComponent { state = { loaded: false, + error: false, }; handleMouseEnter = (e) => { @@ -81,6 +82,10 @@ class Item extends PureComponent { this.setState({ loaded: true }); }; + handleImageError = () => { + this.setState({ error: true }); + }; + render () { const { attachment, lang, index, size, standalone, displayWidth, visible } = this.props; @@ -148,6 +153,7 @@ class Item extends PureComponent { lang={lang} style={{ objectPosition: `${x}% ${y}%` }} onLoad={this.handleImageLoad} + onError={this.handleImageError} /> </a> ); @@ -183,7 +189,7 @@ class Item extends PureComponent { } return ( - <div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}> + <div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--error': this.state.error, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}> <Blurhash hash={attachment.get('blurhash')} dummy={!useBlurhash} diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.tsx b/app/javascript/mastodon/features/account_gallery/components/media_item.tsx index 3de2a29b18..0d251ff99f 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.tsx +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.tsx @@ -26,11 +26,16 @@ export const MediaItem: React.FC<{ displayMedia === 'show_all', ); const [loaded, setLoaded] = useState(false); + const [error, setError] = useState(false); const handleImageLoad = useCallback(() => { setLoaded(true); }, [setLoaded]); + const handleImageError = useCallback(() => { + setError(true); + }, [setError]); + const handleMouseEnter = useCallback( (e: React.MouseEvent<HTMLVideoElement>) => { if (e.target instanceof HTMLVideoElement) { @@ -98,6 +103,7 @@ export const MediaItem: React.FC<{ alt={description} lang={lang} onLoad={handleImageLoad} + onError={handleImageError} /> <div className='media-gallery__item__overlay media-gallery__item__overlay--corner'> @@ -118,6 +124,7 @@ export const MediaItem: React.FC<{ lang={lang} style={{ objectPosition: `${x}% ${y}%` }} onLoad={handleImageLoad} + onError={handleImageError} /> ); } else if (['video', 'gifv'].includes(type)) { @@ -173,7 +180,11 @@ export const MediaItem: React.FC<{ } return ( - <div className='media-gallery__item media-gallery__item--square'> + <div + className={classNames('media-gallery__item media-gallery__item--square', { + 'media-gallery__item--error': error, + })} + > <Blurhash hash={blurhash} className={classNames('media-gallery__preview', { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 011f8263f4..097a2fa20e 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6902,6 +6902,10 @@ a.status-card { filter: var(--overlay-icon-shadow); } } + + &--error img { + visibility: hidden; + } } .media-gallery__item-thumbnail {