/* * Wire * Copyright (C) 2018 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * */ import React, {useEffect, useRef, useState} from 'react'; import {CSSObject} from '@emotion/react'; import cx from 'classnames'; import {container} from 'tsyringe'; import {InViewport} from 'Components/InViewport'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {getImageStyle, getWrapperStyles} from './Image.styles'; import {RestrictedImage} from './RestrictedImage'; import {AssetRemoteData} from '../../assets/AssetRemoteData'; import {Config} from '../../Config'; import {MediumImage} from '../../entity/message/MediumImage'; import {TeamState} from '../../team/TeamState'; import {AssetUrl, useAssetTransfer} from '../MessagesList/Message/ContentMessage/asset/useAssetTransfer'; interface BaseImageProps extends React.HTMLProps { alt?: string; isQuote?: boolean; teamState?: TeamState; imageStyles?: CSSObject; } interface RemoteDataImageProps extends BaseImageProps { image: AssetRemoteData; imageSizes?: {width: string; height: string; ratio: number}; } export interface AssetImageProps extends BaseImageProps { image: MediumImage; } export const AssetImage = ({image, alt, ...props}: AssetImageProps) => { const {resource} = useKoSubscribableChildren(image, ['resource']); return {alt}; }; export const Image = ({ image, imageSizes, onClick, className, isQuote = false, teamState = container.resolve(TeamState), alt, imageStyles, ...props }: RemoteDataImageProps) => { const [isInViewport, setIsInViewport] = useState(false); /** keeps track of whether the component is mounted or not to avoid setting the image url in case it's not */ const isUnmouted = useRef(false); const [imageUrl, setImageUrl] = useState(); const {getAssetUrl} = useAssetTransfer(); const {isFileSharingReceivingEnabled} = useKoSubscribableChildren(teamState, ['isFileSharingReceivingEnabled']); useEffect(() => { if (!imageUrl && isInViewport && image && isFileSharingReceivingEnabled) { void (async () => { try { const allowedImageTypes = [ 'application/octet-stream', // Octet-stream is required to paste images from clipboard ...Config.getConfig().ALLOWED_IMAGE_TYPES, ]; const url = await getAssetUrl(image, allowedImageTypes); if (isUnmouted.current) { // Avoid re-rendering a component that is umounted return; } setImageUrl(url); } catch (error) { console.error(error); } })(); } }, [imageUrl, isInViewport, image, isFileSharingReceivingEnabled, getAssetUrl]); useEffect(() => { return () => { isUnmouted.current = true; imageUrl?.dispose(); }; }, [imageUrl]); if (!isFileSharingReceivingEnabled) { return ; } const dummyImageUrl = `data:image/svg+xml;utf8,`; const assetUrl = imageUrl?.url || dummyImageUrl; const isLoading = !imageUrl; return ( setIsInViewport(true)} css={getWrapperStyles(!!onClick)} className={cx(className, {'loading-dots image-asset--no-image': isLoading})} onClick={event => { if (!isLoading) { onClick?.(event); } }} data-uie-status={isLoading ? 'loading' : 'loaded'} {...props} > {alt} ); };