// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ReactNode } from 'react'; import React, { useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import { Blurhash } from 'react-blurhash'; import type { AttachmentType } from '../types/Attachment'; import type { LocalizerType } from '../types/Util'; import { Spinner } from './Spinner'; import { TextAttachment } from './TextAttachment'; import { ThemeType } from '../types/Util'; import { defaultBlurHash, hasFailed, hasNotResolved, isDownloaded, isDownloading, isGIF, } from '../types/Attachment'; import { getClassNamesFor } from '../util/getClassNamesFor'; import { isVideoTypeSupported } from '../util/GoogleChrome'; export type PropsType = { readonly attachment?: AttachmentType; readonly children?: ReactNode; readonly firstName: string; readonly i18n: LocalizerType; readonly isMe?: boolean; readonly isMuted?: boolean; readonly isPaused?: boolean; readonly isThumbnail?: boolean; readonly label: string; readonly moduleClassName?: string; readonly queueStoryDownload: (storyId: string) => unknown; readonly storyId: string; }; export const StoryImage = ({ attachment, children, firstName, i18n, isMe, isMuted, isPaused, isThumbnail, label, moduleClassName, queueStoryDownload, storyId, }: PropsType): JSX.Element | null => { const shouldDownloadAttachment = (!isDownloaded(attachment) && !isDownloading(attachment)) || hasNotResolved(attachment); const [hasImgError, setHasImgError] = useState(false); const videoRef = useRef(null); useEffect(() => { if (shouldDownloadAttachment) { queueStoryDownload(storyId); } }, [queueStoryDownload, shouldDownloadAttachment, storyId]); useEffect(() => { if (!videoRef.current) { return; } if (isPaused) { videoRef.current.pause(); } else { videoRef.current.play(); } }, [isPaused]); if (!attachment) { return null; } const hasError = hasImgError || hasFailed(attachment); const isPending = Boolean(attachment.pending) && !attachment.textAttachment && !hasError; const isNotReadyToShow = hasNotResolved(attachment) || isPending || hasError; const isSupportedVideo = isVideoTypeSupported(attachment.contentType); const getClassName = getClassNamesFor('StoryImage', moduleClassName); let storyElement: JSX.Element; if (attachment.textAttachment) { storyElement = ( ); } else if (isNotReadyToShow) { storyElement = ( ); } else if (!isThumbnail && isSupportedVideo) { const shouldLoop = isGIF(attachment ? [attachment] : undefined); storyElement = ( ); } else { storyElement = ( {label} setHasImgError(true)} src={ isThumbnail && attachment.thumbnail ? attachment.thumbnail.url : attachment.url } /> ); } let overlay: JSX.Element | undefined; if (isPending) { overlay = (
); } else if (hasError) { let content =
; if (!isThumbnail) { if (isMe) { content = <>{i18n('StoryImage__error--you')}; } else { content = <>{i18n('StoryImage__error2', [firstName])}; } } overlay =
{content}
; } return (
{storyElement} {overlay} {children}
); };