mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at samuel/patch-onpaste 236 lines 7.0 kB view raw
1import { 2 createContext, 3 useCallback, 4 useContext, 5 useEffect, 6 useRef, 7 useState, 8} from 'react' 9import {View} from 'react-native' 10import {type AppBskyEmbedVideo} from '@atproto/api' 11import {msg} from '@lingui/macro' 12import {useLingui} from '@lingui/react' 13import type React from 'react' 14 15import {isFirefox} from '#/lib/browser' 16import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 17import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage' 18import {atoms as a} from '#/alf' 19import {useIsWithinMessage} from '#/components/dms/MessageContext' 20import {useFullscreen} from '#/components/hooks/useFullscreen' 21import { 22 HLSUnsupportedError, 23 VideoEmbedInnerWeb, 24 VideoNotFoundError, 25} from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb' 26import {useActiveVideoWeb} from './ActiveVideoWebContext' 27import * as VideoFallback from './VideoEmbedInner/VideoFallback' 28 29export function VideoEmbed({ 30 embed, 31 crop, 32}: { 33 embed: AppBskyEmbedVideo.View 34 crop?: 'none' | 'square' | 'constrained' 35}) { 36 const ref = useRef<HTMLDivElement>(null) 37 const {active, setActive, sendPosition, currentActiveView} = 38 useActiveVideoWeb() 39 const [onScreen, setOnScreen] = useState(false) 40 const [isFullscreen] = useFullscreen() 41 const lastKnownTime = useRef<number | undefined>() 42 43 useEffect(() => { 44 if (!ref.current) return 45 if (isFullscreen && !isFirefox) return 46 const observer = new IntersectionObserver( 47 entries => { 48 const entry = entries[0] 49 if (!entry) return 50 setOnScreen(entry.isIntersecting) 51 sendPosition( 52 entry.boundingClientRect.y + entry.boundingClientRect.height / 2, 53 ) 54 }, 55 {threshold: 0.5}, 56 ) 57 observer.observe(ref.current) 58 return () => observer.disconnect() 59 }, [sendPosition, isFullscreen]) 60 61 const [key, setKey] = useState(0) 62 const renderError = useCallback( 63 (error: unknown) => ( 64 <VideoError error={error} retry={() => setKey(key + 1)} /> 65 ), 66 [key], 67 ) 68 69 let aspectRatio: number | undefined 70 const dims = embed.aspectRatio 71 if (dims) { 72 aspectRatio = dims.width / dims.height 73 if (Number.isNaN(aspectRatio)) { 74 aspectRatio = undefined 75 } 76 } 77 78 let constrained: number | undefined 79 let max: number | undefined 80 if (aspectRatio !== undefined) { 81 const ratio = 1 / 2 // max of 1:2 ratio in feeds 82 constrained = Math.max(aspectRatio, ratio) 83 max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread 84 } 85 const cropDisabled = crop === 'none' 86 87 const contents = ( 88 <div 89 ref={ref} 90 style={{display: 'flex', flex: 1, cursor: 'default'}} 91 onClick={evt => evt.stopPropagation()}> 92 <ErrorBoundary renderError={renderError} key={key}> 93 <OnlyNearScreen> 94 <VideoEmbedInnerWeb 95 embed={embed} 96 active={active} 97 setActive={setActive} 98 onScreen={onScreen} 99 lastKnownTime={lastKnownTime} 100 /> 101 </OnlyNearScreen> 102 </ErrorBoundary> 103 </div> 104 ) 105 106 return ( 107 <View style={[a.pt_xs]}> 108 <ViewportObserver 109 sendPosition={sendPosition} 110 isAnyViewActive={currentActiveView !== null}> 111 {cropDisabled ? ( 112 <View style={[a.w_full, a.overflow_hidden, {aspectRatio: max ?? 1}]}> 113 {contents} 114 </View> 115 ) : ( 116 <ConstrainedImage 117 fullBleed={crop === 'square'} 118 aspectRatio={constrained || 1}> 119 {contents} 120 </ConstrainedImage> 121 )} 122 </ViewportObserver> 123 </View> 124 ) 125} 126 127const NearScreenContext = createContext(false) 128NearScreenContext.displayName = 'VideoNearScreenContext' 129 130/** 131 * Renders a 100vh tall div and watches it with an IntersectionObserver to 132 * send the position of the div when it's near the screen. 133 * 134 * IMPORTANT: ViewportObserver _must_ not be within a `overflow: hidden` container. 135 */ 136function ViewportObserver({ 137 children, 138 sendPosition, 139 isAnyViewActive, 140}: { 141 children: React.ReactNode 142 sendPosition: (position: number) => void 143 isAnyViewActive: boolean 144}) { 145 const ref = useRef<HTMLDivElement>(null) 146 const [nearScreen, setNearScreen] = useState(false) 147 const [isFullscreen] = useFullscreen() 148 const isWithinMessage = useIsWithinMessage() 149 150 // Send position when scrolling. This is done with an IntersectionObserver 151 // observing a div of 100vh height 152 useEffect(() => { 153 if (!ref.current) return 154 if (isFullscreen && !isFirefox) return 155 const observer = new IntersectionObserver( 156 entries => { 157 const entry = entries[0] 158 if (!entry) return 159 const position = 160 entry.boundingClientRect.y + entry.boundingClientRect.height / 2 161 sendPosition(position) 162 setNearScreen(entry.isIntersecting) 163 }, 164 {threshold: Array.from({length: 101}, (_, i) => i / 100)}, 165 ) 166 observer.observe(ref.current) 167 return () => observer.disconnect() 168 }, [sendPosition, isFullscreen]) 169 170 // In case scrolling hasn't started yet, send up the position 171 useEffect(() => { 172 if (ref.current && !isAnyViewActive) { 173 const rect = ref.current.getBoundingClientRect() 174 const position = rect.y + rect.height / 2 175 sendPosition(position) 176 } 177 }, [isAnyViewActive, sendPosition]) 178 179 return ( 180 <View style={[a.flex_1, a.flex_row]}> 181 <NearScreenContext.Provider value={nearScreen}> 182 {children} 183 </NearScreenContext.Provider> 184 <div 185 ref={ref} 186 style={{ 187 // Don't escape bounds when in a message 188 ...(isWithinMessage 189 ? {top: 0, height: '100%'} 190 : {top: 'calc(50% - 50vh)', height: '100vh'}), 191 position: 'absolute', 192 left: '50%', 193 width: 1, 194 pointerEvents: 'none', 195 }} 196 /> 197 </View> 198 ) 199} 200 201/** 202 * Awkward data flow here, but we need to hide the video when it's not near the screen. 203 * But also, ViewportObserver _must_ not be within a `overflow: hidden` container. 204 * So we put it at the top level of the component tree here, then hide the children of 205 * the auto-resizing container. 206 */ 207export const OnlyNearScreen = ({children}: {children: React.ReactNode}) => { 208 const nearScreen = useContext(NearScreenContext) 209 210 return nearScreen ? children : null 211} 212 213function VideoError({error, retry}: {error: unknown; retry: () => void}) { 214 const {_} = useLingui() 215 216 let showRetryButton = true 217 let text = null 218 219 if (error instanceof VideoNotFoundError) { 220 text = _(msg`Video not found.`) 221 } else if (error instanceof HLSUnsupportedError) { 222 showRetryButton = false 223 text = _( 224 msg`Your browser does not support the video format. Please try a different browser.`, 225 ) 226 } else { 227 text = _(msg`An error occurred while loading the video. Please try again.`) 228 } 229 230 return ( 231 <VideoFallback.Container> 232 <VideoFallback.Text>{text}</VideoFallback.Text> 233 {showRetryButton && <VideoFallback.RetryButton onPress={retry} />} 234 </VideoFallback.Container> 235 ) 236}