import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './Media.scss'; import encrypt from 'browser-encrypt-attachment'; import { BlurhashCanvas } from 'react-blurhash'; import Text from '../../atoms/text/Text'; import IconButton from '../../atoms/button/IconButton'; import Spinner from '../../atoms/spinner/Spinner'; import DownloadSVG from '../../../../public/res/ic/outlined/download.svg'; import ExternalSVG from '../../../../public/res/ic/outlined/external.svg'; import PlaySVG from '../../../../public/res/ic/outlined/play.svg'; import { getBlobSafeMimeType } from '../../../util/mimetypes'; async function getDecryptedBlob(response, type, decryptData) { const arrayBuffer = await response.arrayBuffer(); const dataArray = await encrypt.decryptAttachment(arrayBuffer, decryptData); const blob = new Blob([dataArray], { type: getBlobSafeMimeType(type) }); return blob; } async function getUrl(link, type, decryptData) { try { const response = await fetch(link, { method: 'GET' }); if (decryptData !== null) { return URL.createObjectURL(await getDecryptedBlob(response, type, decryptData)); } const blob = await response.blob(); return URL.createObjectURL(blob); } catch (e) { return link; } } function getNativeHeight(width, height, maxWidth = 296) { const scale = maxWidth / width; return scale * height; } function FileHeader({ name, link, external, file, type, }) { const [url, setUrl] = useState(null); async function getFile() { const myUrl = await getUrl(link, type, file); setUrl(myUrl); } async function handleDownload(e) { if (file !== null && url === null) { e.preventDefault(); await getFile(); e.target.click(); } } return (
{name} { link !== null && ( <> { external && ( window.open(url || link)} /> ) } )}
); } FileHeader.defaultProps = { external: false, file: null, link: null, }; FileHeader.propTypes = { name: PropTypes.string.isRequired, link: PropTypes.string, external: PropTypes.bool, file: PropTypes.shape({}), type: PropTypes.string.isRequired, }; function File({ name, link, file, type, }) { return (
); } File.defaultProps = { file: null, type: '', }; File.propTypes = { name: PropTypes.string.isRequired, link: PropTypes.string.isRequired, type: PropTypes.string, file: PropTypes.shape({}), }; function Image({ name, width, height, link, file, type, blurhash, }) { const [url, setUrl] = useState(null); const [blur, setBlur] = useState(true); useEffect(() => { let unmounted = false; async function fetchUrl() { const myUrl = await getUrl(link, type, file); if (unmounted) return; setUrl(myUrl); } fetchUrl(); return () => { unmounted = true; }; }, []); return (
{ blurhash && blur && } { url !== null && setBlur(false)} src={url || link} alt={name} />}
); } Image.defaultProps = { file: null, width: null, height: null, type: '', blurhash: '', }; Image.propTypes = { name: PropTypes.string.isRequired, width: PropTypes.number, height: PropTypes.number, link: PropTypes.string.isRequired, file: PropTypes.shape({}), type: PropTypes.string, blurhash: PropTypes.string, }; function Sticker({ name, height, width, link, file, type, }) { const [url, setUrl] = useState(null); useEffect(() => { let unmounted = false; async function fetchUrl() { const myUrl = await getUrl(link, type, file); if (unmounted) return; setUrl(myUrl); } fetchUrl(); return () => { unmounted = true; }; }, []); return (
{ url !== null && {name}}
); } Sticker.defaultProps = { file: null, type: '', width: null, height: null, }; Sticker.propTypes = { name: PropTypes.string.isRequired, width: PropTypes.number, height: PropTypes.number, link: PropTypes.string.isRequired, file: PropTypes.shape({}), type: PropTypes.string, }; function Audio({ name, link, type, file, }) { const [isLoading, setIsLoading] = useState(false); const [url, setUrl] = useState(null); async function loadAudio() { const myUrl = await getUrl(link, type, file); setUrl(myUrl); setIsLoading(false); } function handlePlayAudio() { setIsLoading(true); loadAudio(); } return (
{ url === null && isLoading && } { url === null && !isLoading && } { url !== null && ( /* eslint-disable-next-line jsx-a11y/media-has-caption */ )}
); } Audio.defaultProps = { file: null, type: '', }; Audio.propTypes = { name: PropTypes.string.isRequired, link: PropTypes.string.isRequired, type: PropTypes.string, file: PropTypes.shape({}), }; function Video({ name, link, thumbnail, thumbnailFile, thumbnailType, width, height, file, type, blurhash, }) { const [isLoading, setIsLoading] = useState(false); const [url, setUrl] = useState(null); const [thumbUrl, setThumbUrl] = useState(null); const [blur, setBlur] = useState(true); useEffect(() => { let unmounted = false; async function fetchUrl() { const myThumbUrl = await getUrl(thumbnail, thumbnailType, thumbnailFile); if (unmounted) return; setThumbUrl(myThumbUrl); } if (thumbnail !== null) fetchUrl(); return () => { unmounted = true; }; }, []); const loadVideo = async () => { const myUrl = await getUrl(link, type, file); setUrl(myUrl); setIsLoading(false); }; const handlePlayVideo = () => { setIsLoading(true); loadVideo(); }; return (
{ url === null ? ( <> { blurhash && blur && } { thumbUrl !== null && ( setBlur(false)} alt={name} /> )} {isLoading && } {!isLoading && } ) : ( /* eslint-disable-next-line jsx-a11y/media-has-caption */ )}
); } Video.defaultProps = { width: null, height: null, file: null, thumbnail: null, thumbnailType: null, thumbnailFile: null, type: '', blurhash: null, }; Video.propTypes = { name: PropTypes.string.isRequired, link: PropTypes.string.isRequired, thumbnail: PropTypes.string, thumbnailFile: PropTypes.shape({}), thumbnailType: PropTypes.string, width: PropTypes.number, height: PropTypes.number, file: PropTypes.shape({}), type: PropTypes.string, blurhash: PropTypes.string, }; export { File, Image, Sticker, Audio, Video, };