From a04433f995099854c06fbc7d02245a7d2a3677c0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 26 Sep 2024 15:26:49 +0200 Subject: [PATCH] Add ability to view alt text by clicking the ALT badge in web UI (#32058) --- .../mastodon/components/alt_text_badge.tsx | 67 +++++++++++++++++++ .../mastodon/components/media_gallery.jsx | 7 +- .../account_gallery/components/media_item.tsx | 17 +++-- app/javascript/mastodon/locales/en.json | 1 + .../styles/mastodon/components.scss | 45 +++++++++++-- 5 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 app/javascript/mastodon/components/alt_text_badge.tsx diff --git a/app/javascript/mastodon/components/alt_text_badge.tsx b/app/javascript/mastodon/components/alt_text_badge.tsx new file mode 100644 index 0000000000..99bec1ee51 --- /dev/null +++ b/app/javascript/mastodon/components/alt_text_badge.tsx @@ -0,0 +1,67 @@ +import { useState, useCallback, useRef } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import Overlay from 'react-overlays/Overlay'; +import type { + OffsetValue, + UsePopperOptions, +} from 'react-overlays/esm/usePopper'; + +const offset = [0, 4] as OffsetValue; +const popperConfig = { strategy: 'fixed' } as UsePopperOptions; + +export const AltTextBadge: React.FC<{ + description: string; +}> = ({ description }) => { + const anchorRef = useRef(null); + const [open, setOpen] = useState(false); + + const handleClick = useCallback(() => { + setOpen((v) => !v); + }, [setOpen]); + + const handleClose = useCallback(() => { + setOpen(false); + }, [setOpen]); + + return ( + <> + + + + {({ props }) => ( +
+
+

+ +

+

{description}

+
+
+ )} +
+ + ); +}; diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index 35924008b5..84cb4e04dc 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -10,6 +10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { debounce } from 'lodash'; +import { AltTextBadge } from 'mastodon/components/alt_text_badge'; import { Blurhash } from 'mastodon/components/blurhash'; import { formatTime } from 'mastodon/features/video'; @@ -97,7 +98,7 @@ class Item extends PureComponent { } if (attachment.get('description')?.length > 0) { - badges.push(ALT); + badges.push(); } const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); @@ -156,9 +157,9 @@ class Item extends PureComponent { const duration = attachment.getIn(['meta', 'original', 'duration']); if (attachment.get('type') === 'gifv') { - badges.push(GIF); + badges.push(GIF); } else { - badges.push({formatTime(Math.floor(duration))}); + badges.push({formatTime(Math.floor(duration))}); } thumbnail = ( 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 1a294a74a0..729e40a993 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.tsx +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.tsx @@ -5,6 +5,7 @@ import classNames from 'classnames'; import HeadphonesIcon from '@/material-icons/400-24px/headphones-fill.svg?react'; import MovieIcon from '@/material-icons/400-24px/movie-fill.svg?react'; import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import { AltTextBadge } from 'mastodon/components/alt_text_badge'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; import { formatTime } from 'mastodon/features/video'; @@ -77,11 +78,7 @@ export const MediaItem: React.FC<{ const badges = []; if (description && description.length > 0) { - badges.push( - - ALT - , - ); + badges.push(); } if (!visible) { @@ -156,13 +153,19 @@ export const MediaItem: React.FC<{ if (type === 'gifv') { badges.push( - + GIF , ); } else { badges.push( - + {formatTime(Math.floor(duration))} , ); diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index b20934388c..e86e300bcb 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -85,6 +85,7 @@ "alert.rate_limited.title": "Rate limited", "alert.unexpected.message": "An unexpected error occurred.", "alert.unexpected.title": "Oops!", + "alt_text_badge.title": "Alt text", "announcement.announcement": "Announcement", "attachments_list.unprocessed": "(unprocessed)", "audio.hide": "Hide audio", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f3464c83b7..8cb63e42e6 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6971,14 +6971,14 @@ a.status-card { inset-inline-end: 8px; display: flex; gap: 2px; + pointer-events: none; } -.media-gallery__alt__label, -.media-gallery__gifv__label { - display: flex; - align-items: center; - justify-content: center; +.media-gallery__alt__label { + display: block; + text-align: center; color: $white; + border: 0; background: rgba($black, 0.65); backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%); padding: 3px 8px; @@ -6986,8 +6986,41 @@ a.status-card { font-size: 12px; font-weight: 700; z-index: 1; - pointer-events: none; line-height: 20px; + cursor: pointer; + pointer-events: auto; + + &--non-interactive { + pointer-events: none; + } +} + +.media-gallery__alt__popover { + background: rgba($black, 0.65); + backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%); + border-radius: 4px; + box-shadow: var(--dropdown-shadow); + padding: 16px; + min-width: 16em; + min-height: 2em; + max-width: 22em; + max-height: 30em; + overflow-y: auto; + + h4 { + font-size: 15px; + line-height: 20px; + font-weight: 500; + color: $white; + margin-bottom: 8px; + } + + p { + font-size: 15px; + line-height: 20px; + color: rgba($white, 0.85); + white-space: pre-line; + } } .attachment-list {