forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useMemo} from 'react'
2import {type StyleProp, View, type ViewStyle} from 'react-native'
3
4import {cleanError} from '#/lib/strings/errors'
5import {
6 useResolveGifQuery,
7 useResolveLinkQuery,
8} from '#/state/queries/resolve-link'
9import {type Gif} from '#/state/queries/tenor'
10import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'
11import {atoms as a, useTheme} from '#/alf'
12import {Loader} from '#/components/Loader'
13import {ExternalEmbed} from '#/components/Post/Embed/ExternalEmbed'
14import {ModeratedFeedEmbed} from '#/components/Post/Embed/FeedEmbed'
15import {ModeratedListEmbed} from '#/components/Post/Embed/ListEmbed'
16import {Embed as StarterPackEmbed} from '#/components/StarterPack/StarterPackCard'
17import {Text} from '#/components/Typography'
18
19export const ExternalEmbedGif = ({
20 onRemove,
21 gif,
22}: {
23 onRemove: () => void
24 gif: Gif
25}) => {
26 const t = useTheme()
27 const {data, error} = useResolveGifQuery(gif)
28 const linkInfo = useMemo(
29 () =>
30 data && {
31 title: data.title ?? data.uri,
32 uri: data.uri,
33 description: data.description ?? '',
34 thumb: data.thumb?.source.path,
35 },
36 [data],
37 )
38
39 const loadingStyle: ViewStyle = {
40 aspectRatio: (() => {
41 const dims = gif.media_formats.gif?.dims
42 if (dims && dims[0] > 0 && dims[1] > 0) {
43 return dims[0] / dims[1]
44 }
45 return 16 / 9 // Default aspect ratio
46 })(),
47 width: '100%',
48 }
49
50 return (
51 <View style={[a.overflow_hidden, t.atoms.border_contrast_medium]}>
52 {linkInfo ? (
53 <View style={{pointerEvents: 'auto'}}>
54 <ExternalEmbed link={linkInfo} hideAlt />
55 </View>
56 ) : error ? (
57 <Container style={[a.align_start, a.p_md, a.gap_xs]}>
58 <Text numberOfLines={1} style={t.atoms.text_contrast_high}>
59 {gif.url}
60 </Text>
61 <Text numberOfLines={2} style={[{color: t.palette.negative_400}]}>
62 {cleanError(error)}
63 </Text>
64 </Container>
65 ) : (
66 <Container style={loadingStyle}>
67 <Loader size="xl" />
68 </Container>
69 )}
70 <ExternalEmbedRemoveBtn onRemove={onRemove} />
71 </View>
72 )
73}
74
75export const ExternalEmbedLink = ({
76 uri,
77 hasQuote,
78 onRemove,
79}: {
80 uri: string
81 hasQuote: boolean
82 onRemove: () => void
83}) => {
84 const t = useTheme()
85 const {data, error} = useResolveLinkQuery(uri)
86 const linkComponent = useMemo(() => {
87 if (data) {
88 if (data.type === 'external') {
89 return (
90 <ExternalEmbed
91 link={{
92 title: data.title || uri,
93 uri,
94 description: data.description,
95 thumb: data.thumb?.source.path,
96 }}
97 hideAlt
98 />
99 )
100 } else if (data.kind === 'feed') {
101 return (
102 <ModeratedFeedEmbed
103 embed={{
104 type: 'feed',
105 view: {
106 $type: 'app.bsky.feed.defs#generatorView',
107 ...data.view,
108 },
109 }}
110 />
111 )
112 } else if (data.kind === 'list') {
113 return (
114 <ModeratedListEmbed
115 embed={{
116 type: 'list',
117 view: {
118 $type: 'app.bsky.graph.defs#listView',
119 ...data.view,
120 },
121 }}
122 />
123 )
124 } else if (data.kind === 'starter-pack') {
125 return <StarterPackEmbed starterPack={data.view} />
126 }
127 }
128 }, [data, uri])
129
130 if (data?.type === 'record' && hasQuote) {
131 // This is not currently supported by the data model so don't preview it.
132 return null
133 }
134
135 return (
136 <View style={[a.mb_xl, a.overflow_hidden, t.atoms.border_contrast_medium]}>
137 {linkComponent ? (
138 <View style={{pointerEvents: 'none'}}>{linkComponent}</View>
139 ) : error ? (
140 <Container style={[a.align_start, a.p_md, a.gap_xs]}>
141 <Text numberOfLines={1} style={t.atoms.text_contrast_high}>
142 {uri}
143 </Text>
144 <Text numberOfLines={2} style={[{color: t.palette.negative_400}]}>
145 {cleanError(error)}
146 </Text>
147 </Container>
148 ) : (
149 <Container>
150 <Loader size="xl" />
151 </Container>
152 )}
153 <ExternalEmbedRemoveBtn onRemove={onRemove} />
154 </View>
155 )
156}
157
158function Container({
159 style,
160 children,
161}: {
162 style?: StyleProp<ViewStyle>
163 children: React.ReactNode
164}) {
165 const t = useTheme()
166 return (
167 <View
168 style={[
169 a.rounded_sm,
170 a.border,
171 a.align_center,
172 a.justify_center,
173 a.py_5xl,
174 t.atoms.bg_contrast_25,
175 t.atoms.border_contrast_medium,
176 style,
177 ]}>
178 {children}
179 </View>
180 )
181}