bsky-app/src/view/com/util/post-embeds/index.tsx

196 lines
4.9 KiB
TypeScript

import React from 'react'
import {
InteractionManager,
StyleProp,
StyleSheet,
Text,
View,
ViewStyle,
} from 'react-native'
import {Image} from 'expo-image'
import {
AppBskyEmbedExternal,
AppBskyEmbedImages,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
AppBskyGraphDefs,
ModerationDecision,
} from '@atproto/api'
import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
import {usePalette} from 'lib/hooks/usePalette'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
import {ContentHider} from '../../../../components/moderation/ContentHider'
import {AutoSizedImage} from '../images/AutoSizedImage'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
import {ListEmbed} from './ListEmbed'
import {MaybeQuoteEmbed} from './QuoteEmbed'
type Embed =
| AppBskyEmbedRecord.View
| AppBskyEmbedImages.View
| AppBskyEmbedExternal.View
| AppBskyEmbedRecordWithMedia.View
| {$type: string; [k: string]: unknown}
export function PostEmbeds({
embed,
moderation,
onOpen,
style,
}: {
embed?: Embed
moderation?: ModerationDecision
onOpen?: () => void
style?: StyleProp<ViewStyle>
}) {
const pal = usePalette('default')
const {openLightbox} = useLightboxControls()
// quote post with media
// =
if (AppBskyEmbedRecordWithMedia.isView(embed)) {
return (
<View style={style}>
<PostEmbeds
embed={embed.media}
moderation={moderation}
onOpen={onOpen}
/>
<MaybeQuoteEmbed embed={embed.record} onOpen={onOpen} />
</View>
)
}
if (AppBskyEmbedRecord.isView(embed)) {
// custom feed embed (i.e. generator view)
// =
if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
// TODO moderation
return (
<FeedSourceCard
feedUri={embed.record.uri}
style={[pal.view, pal.border, styles.customFeedOuter]}
showLikes
/>
)
}
// list embed
if (AppBskyGraphDefs.isListView(embed.record)) {
// TODO moderation
return <ListEmbed item={embed.record} />
}
// quote post
// =
return <MaybeQuoteEmbed embed={embed} style={style} onOpen={onOpen} />
}
// image embed
// =
if (AppBskyEmbedImages.isView(embed)) {
const {images} = embed
if (images.length > 0) {
const items = embed.images.map(img => ({
uri: img.fullsize,
alt: img.alt,
aspectRatio: img.aspectRatio,
}))
const _openLightbox = (index: number) => {
openLightbox(new ImagesLightbox(items, index))
}
const onPressIn = (_: number) => {
InteractionManager.runAfterInteractions(() => {
Image.prefetch(items.map(i => i.uri))
})
}
if (images.length === 1) {
const {alt, thumb, aspectRatio} = images[0]
return (
<ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[styles.imagesContainer, style]}>
<AutoSizedImage
alt={alt}
uri={thumb}
dimensionsHint={aspectRatio}
onPress={() => _openLightbox(0)}
onPressIn={() => onPressIn(0)}
style={[styles.singleImage]}>
{alt === '' ? null : (
<View style={styles.altContainer}>
<Text style={styles.alt} accessible={false}>
ALT
</Text>
</View>
)}
</AutoSizedImage>
</View>
</ContentHider>
)
}
return (
<ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[styles.imagesContainer, style]}>
<ImageLayoutGrid
images={embed.images}
onPress={_openLightbox}
onPressIn={onPressIn}
style={
embed.images.length === 1 ? [styles.singleImage] : undefined
}
/>
</View>
</ContentHider>
)
}
}
// external link embed
// =
if (AppBskyEmbedExternal.isView(embed)) {
const link = embed.external
return (
<ContentHider modui={moderation?.ui('contentMedia')}>
<ExternalLinkEmbed link={link} onOpen={onOpen} style={style} />
</ContentHider>
)
}
return <View />
}
const styles = StyleSheet.create({
imagesContainer: {
marginTop: 8,
},
singleImage: {
borderRadius: 8,
},
altContainer: {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
borderRadius: 6,
paddingHorizontal: 6,
paddingVertical: 3,
position: 'absolute',
right: 6,
bottom: 6,
},
alt: {
color: 'white',
fontSize: 6,
fontWeight: 'bold',
},
customFeedOuter: {
borderWidth: 1,
borderRadius: 8,
marginTop: 4,
paddingHorizontal: 12,
paddingVertical: 12,
},
})