Constrain image heights in feeds and threads (#5129)
* Limit height of images within posts * Add some future-proofness * Comments, improve a11y * Adjust ALT, add crop icon * Fix disableCrop in record-with-media posts * Clean up aspect ratios, handle very tall images * Handle record-with-media separately, clarify intent using enums * Adjust spacing * Adjust rwm embed image size on mobile * Only do reduced layout if images embed * Adjust gap in small embed variant * Clean up grid layout * Hide badge on small variant with one image * Remove crop icon from image grid, leave on single image * Fix sizing in Firefox * Fix fullBleed variant
This commit is contained in:
parent
117926357d
commit
2265fedd2a
12 changed files with 397 additions and 207 deletions
|
|
@ -33,7 +33,7 @@ import {InfoCircleIcon} from 'lib/icons'
|
|||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {precacheProfile} from 'state/queries/profile'
|
||||
import {ComposerOptsQuote} from 'state/shell/composer'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {atoms as a, useBreakpoints} from '#/alf'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {ContentHider} from '../../../../components/moderation/ContentHider'
|
||||
import {PostAlerts} from '../../../../components/moderation/PostAlerts'
|
||||
|
|
@ -41,17 +41,20 @@ import {Link} from '../Link'
|
|||
import {PostMeta} from '../PostMeta'
|
||||
import {Text} from '../text/Text'
|
||||
import {PostEmbeds} from '.'
|
||||
import {PostEmbedViewContext, QuoteEmbedViewContext} from './types'
|
||||
|
||||
export function MaybeQuoteEmbed({
|
||||
embed,
|
||||
onOpen,
|
||||
style,
|
||||
allowNestedQuotes,
|
||||
viewContext,
|
||||
}: {
|
||||
embed: AppBskyEmbedRecord.View
|
||||
onOpen?: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
allowNestedQuotes?: boolean
|
||||
viewContext?: QuoteEmbedViewContext
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {currentAccount} = useSession()
|
||||
|
|
@ -67,6 +70,7 @@ export function MaybeQuoteEmbed({
|
|||
onOpen={onOpen}
|
||||
style={style}
|
||||
allowNestedQuotes={allowNestedQuotes}
|
||||
viewContext={viewContext}
|
||||
/>
|
||||
)
|
||||
} else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
|
||||
|
|
@ -113,12 +117,14 @@ function QuoteEmbedModerated({
|
|||
onOpen,
|
||||
style,
|
||||
allowNestedQuotes,
|
||||
viewContext,
|
||||
}: {
|
||||
viewRecord: AppBskyEmbedRecord.ViewRecord
|
||||
postRecord: AppBskyFeedPost.Record
|
||||
onOpen?: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
allowNestedQuotes?: boolean
|
||||
viewContext?: QuoteEmbedViewContext
|
||||
}) {
|
||||
const moderationOpts = useModerationOpts()
|
||||
const moderation = React.useMemo(() => {
|
||||
|
|
@ -144,6 +150,7 @@ function QuoteEmbedModerated({
|
|||
onOpen={onOpen}
|
||||
style={style}
|
||||
allowNestedQuotes={allowNestedQuotes}
|
||||
viewContext={viewContext}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -154,18 +161,21 @@ export function QuoteEmbed({
|
|||
onOpen,
|
||||
style,
|
||||
allowNestedQuotes,
|
||||
viewContext,
|
||||
}: {
|
||||
quote: ComposerOptsQuote
|
||||
moderation?: ModerationDecision
|
||||
onOpen?: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
allowNestedQuotes?: boolean
|
||||
viewContext?: QuoteEmbedViewContext
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const pal = usePalette('default')
|
||||
const itemUrip = new AtUri(quote.uri)
|
||||
const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey)
|
||||
const itemTitle = `Post by ${quote.author.handle}`
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const richText = React.useMemo(
|
||||
() =>
|
||||
|
|
@ -197,6 +207,7 @@ export function QuoteEmbed({
|
|||
}
|
||||
}
|
||||
}, [quote.embeds, allowNestedQuotes])
|
||||
const isImagesEmbed = AppBskyEmbedImages.isView(embed)
|
||||
|
||||
const onBeforePress = React.useCallback(() => {
|
||||
precacheProfile(queryClient, quote.author)
|
||||
|
|
@ -226,15 +237,43 @@ export function QuoteEmbed({
|
|||
{moderation ? (
|
||||
<PostAlerts modui={moderation.ui('contentView')} style={[a.py_xs]} />
|
||||
) : null}
|
||||
{richText ? (
|
||||
<RichText
|
||||
value={richText}
|
||||
style={a.text_md}
|
||||
numberOfLines={20}
|
||||
disableLinks
|
||||
/>
|
||||
) : null}
|
||||
{embed && <PostEmbeds embed={embed} moderation={moderation} />}
|
||||
|
||||
{viewContext === QuoteEmbedViewContext.FeedEmbedRecordWithMedia &&
|
||||
isImagesEmbed ? (
|
||||
<View style={[a.flex_row, a.gap_md]}>
|
||||
{embed && (
|
||||
<View style={[{width: gtMobile ? 100 : 80}]}>
|
||||
<PostEmbeds
|
||||
embed={embed}
|
||||
moderation={moderation}
|
||||
viewContext={PostEmbedViewContext.FeedEmbedRecordWithMedia}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{richText ? (
|
||||
<View style={[a.flex_1, a.pt_xs]}>
|
||||
<RichText
|
||||
value={richText}
|
||||
style={a.text_md}
|
||||
numberOfLines={20}
|
||||
disableLinks
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
{richText ? (
|
||||
<RichText
|
||||
value={richText}
|
||||
style={a.text_md}
|
||||
numberOfLines={20}
|
||||
disableLinks
|
||||
/>
|
||||
) : null}
|
||||
{embed && <PostEmbeds embed={embed} moderation={moderation} />}
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
</ContentHider>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
InteractionManager,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
|
|
@ -22,7 +21,6 @@ import {
|
|||
} from '@atproto/api'
|
||||
|
||||
import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
|
||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
||||
|
|
@ -34,8 +32,11 @@ import {AutoSizedImage} from '../images/AutoSizedImage'
|
|||
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
|
||||
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
||||
import {MaybeQuoteEmbed} from './QuoteEmbed'
|
||||
import {PostEmbedViewContext, QuoteEmbedViewContext} from './types'
|
||||
import {VideoEmbed} from './VideoEmbed'
|
||||
|
||||
export * from './types'
|
||||
|
||||
type Embed =
|
||||
| AppBskyEmbedRecord.View
|
||||
| AppBskyEmbedImages.View
|
||||
|
|
@ -50,15 +51,16 @@ export function PostEmbeds({
|
|||
onOpen,
|
||||
style,
|
||||
allowNestedQuotes,
|
||||
viewContext,
|
||||
}: {
|
||||
embed?: Embed
|
||||
moderation?: ModerationDecision
|
||||
onOpen?: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
allowNestedQuotes?: boolean
|
||||
viewContext?: PostEmbedViewContext
|
||||
}) {
|
||||
const {openLightbox} = useLightboxControls()
|
||||
const largeAltBadge = useLargeAltBadgeEnabled()
|
||||
|
||||
// quote post with media
|
||||
// =
|
||||
|
|
@ -69,8 +71,17 @@ export function PostEmbeds({
|
|||
embed={embed.media}
|
||||
moderation={moderation}
|
||||
onOpen={onOpen}
|
||||
viewContext={viewContext}
|
||||
/>
|
||||
<MaybeQuoteEmbed
|
||||
embed={embed.record}
|
||||
onOpen={onOpen}
|
||||
viewContext={
|
||||
viewContext === PostEmbedViewContext.Feed
|
||||
? QuoteEmbedViewContext.FeedEmbedRecordWithMedia
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<MaybeQuoteEmbed embed={embed.record} onOpen={onOpen} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
@ -124,27 +135,26 @@ export function PostEmbeds({
|
|||
}
|
||||
|
||||
if (images.length === 1) {
|
||||
const {alt, thumb, aspectRatio} = images[0]
|
||||
const image = images[0]
|
||||
return (
|
||||
<ContentHider modui={moderation?.ui('contentMedia')}>
|
||||
<View style={[styles.container, style]}>
|
||||
<AutoSizedImage
|
||||
alt={alt}
|
||||
uri={thumb}
|
||||
dimensionsHint={aspectRatio}
|
||||
crop={
|
||||
viewContext === PostEmbedViewContext.ThreadHighlighted
|
||||
? 'none'
|
||||
: viewContext ===
|
||||
PostEmbedViewContext.FeedEmbedRecordWithMedia
|
||||
? 'square'
|
||||
: 'constrained'
|
||||
}
|
||||
image={image}
|
||||
onPress={() => _openLightbox(0)}
|
||||
onPressIn={() => onPressIn(0)}
|
||||
style={a.rounded_sm}>
|
||||
{alt === '' ? null : (
|
||||
<View style={styles.altContainer}>
|
||||
<Text
|
||||
style={[styles.alt, largeAltBadge && a.text_xs]}
|
||||
accessible={false}>
|
||||
ALT
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</AutoSizedImage>
|
||||
hideBadge={
|
||||
viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</ContentHider>
|
||||
)
|
||||
|
|
@ -157,6 +167,7 @@ export function PostEmbeds({
|
|||
images={embed.images}
|
||||
onPress={_openLightbox}
|
||||
onPressIn={onPressIn}
|
||||
viewContext={viewContext}
|
||||
/>
|
||||
</View>
|
||||
</ContentHider>
|
||||
|
|
|
|||
9
src/view/com/util/post-embeds/types.ts
Normal file
9
src/view/com/util/post-embeds/types.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export enum PostEmbedViewContext {
|
||||
ThreadHighlighted = 'ThreadHighlighted',
|
||||
Feed = 'Feed',
|
||||
FeedEmbedRecordWithMedia = 'FeedEmbedRecordWithMedia',
|
||||
}
|
||||
|
||||
export enum QuoteEmbedViewContext {
|
||||
FeedEmbedRecordWithMedia = PostEmbedViewContext.FeedEmbedRecordWithMedia,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue