import React, { memo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils'; import { getHostname, parseHttpUrl } from '../../lib/utils/url-utils'; import useExpandedMediaStore from '../../stores/use-expanded-media-store'; import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; import useIsMobile from '../../hooks/use-is-mobile'; import styles from './comment-media.module.css'; import Embed, { canEmbed } from '../embed'; import RufflePlayer from './ruffle-player'; interface MediaProps { commentMediaInfo?: CommentMediaInfo; deleted?: boolean; disableToggle?: boolean; displayHeight?: string; displayWidth?: string; isFloatingEmbed?: boolean; isOutOfFeed?: boolean; isReply?: boolean; linkHeight?: number; linkWidth?: number; parentCid?: string; purged?: boolean; removed?: boolean; spoiler?: boolean; showThumbnail?: boolean; setShowThumbnail: (showThumbnail: boolean) => void; onMediaLoadFailureChange?: (url: string | undefined) => void; } type GifFrameState = ReturnType; const getMediaLoadFailureLabels = (url: string | undefined, t: ReturnType['t']) => { const hostname = url ? getHostname(url) : undefined; const label = t('media_failed_to_load'); const source = hostname ? t('media_failed_to_load_source', { host: hostname }) : t('media_failed_to_load_source_unknown'); const hint = t('media_failed_to_load_hint'); const openSourceLabel = t('media_failed_to_load_open_source'); const inline = hostname ? t('media_failed_to_load_inline', { host: hostname }) : t('media_failed_to_load_inline_unknown'); const statusLabel = url ? `${label}. ${source}. ${hint}. ${openSourceLabel}.` : `${label}. ${source}. ${hint}.`; return { inline, openSourceLabel, statusLabel }; }; const MediaLoadFailure = ({ compact = false, url }: { compact?: boolean; url?: string }) => { const { t } = useTranslation(); const { inline, openSourceLabel, statusLabel } = getMediaLoadFailureLabels(url, t); if (compact) { return ( <> ); } return ( {inline} {url && ( {openSourceLabel} )} ); }; export const MediaLoadFailureInfo = ({ url }: { url?: string }) => { const { t } = useTranslation(); const { inline, openSourceLabel, statusLabel } = getMediaLoadFailureLabels(url, t); return (
{inline} {url && ( <> {' '} {openSourceLabel} )}
); }; const Thumbnail = ({ commentMediaInfo, deleted, displayHeight, gifFrameState, displayWidth, isFloatingEmbed, isOutOfFeed, isReply, purged, removed, spoiler, setShowThumbnail, }: MediaProps & { gifFrameState: GifFrameState }) => { const isMobile = useIsMobile(); const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {}; let thumbnailComponent: React.ReactNode = null; const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties; const iframeThumbnail = patternThumbnailUrl || thumbnail; const { frameUrl: gifFrameUrl, status: gifFrameStatus } = gifFrameState; const hasThumbnail = getHasThumbnail(commentMediaInfo, url); const handleOpenMedia = () => setShowThumbnail(false); if (type === 'gif') { thumbnailComponent = gifFrameStatus === 'loading' ? ( ); } else if (type === 'video') { thumbnailComponent = thumbnail ? ( Video thumbnail ) : ( // show first frame of the video, as a workaround for Safari not loading thumbnails ); } else if (type === 'webpage') { thumbnailComponent = ( ); } else if (type === 'iframe') { thumbnailComponent = iframeThumbnail ? ( ) : null; } else if (type === 'audio') { thumbnailComponent =