import React, { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { clone } from 'lodash'; import { Data } from '../../../../data/data'; import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType'; import { PropsForAttachment, showLightBox, toggleSelectedMessageId, } from '../../../../state/ducks/conversations'; import { getMessageAttachmentProps, isMessageSelectionMode, } from '../../../../state/selectors/conversations'; import { AttachmentType, AttachmentTypeWithPath, canDisplayImage, getExtensionForDisplay, hasImage, hasVideoScreenshot, isAudio, isImage, isVideo, } from '../../../../types/Attachment'; import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil'; import { Spinner } from '../../../basic/Spinner'; import { AudioPlayerWithEncryptedFile } from '../../H5AudioPlayer'; import { ImageGrid } from '../../ImageGrid'; import { LightBoxOptions } from '../../SessionConversation'; import { ClickToTrustSender } from './ClickToTrustSender'; import styled from 'styled-components'; import classNames from 'classnames'; export type MessageAttachmentSelectorProps = Pick< MessageRenderingProps, | 'isTrustedForAttachmentDownload' | 'direction' | 'timestamp' | 'serverTimestamp' | 'sender' | 'convoId' > & { attachments: Array<PropsForAttachment>; }; type Props = { messageId: string; imageBroken: boolean; handleImageError: () => void; }; // tslint:disable: use-simple-attributes const StyledAttachmentContainer = styled.div<{ messageDirection: MessageModelType }>` text-align: center; position: relative; overflow: hidden; display: flex; justify-content: ${props => (props.messageDirection === 'incoming' ? 'flex-start' : 'flex-end')}; `; // tslint:disable-next-line max-func-body-length cyclomatic-complexity export const MessageAttachment = (props: Props) => { const { messageId, imageBroken, handleImageError } = props; const dispatch = useDispatch(); const attachmentProps = useSelector(state => getMessageAttachmentProps(state as any, messageId)); const multiSelectMode = useSelector(isMessageSelectionMode); const onClickOnImageGrid = useCallback( (attachment: AttachmentTypeWithPath | AttachmentType) => { if (multiSelectMode) { dispatch(toggleSelectedMessageId(messageId)); } else { void onClickAttachment({ attachment, messageId, }); } }, [messageId, multiSelectMode] ); const onClickOnGenericAttachment = useCallback( (e: any) => { e.stopPropagation(); e.preventDefault(); if (!attachments?.length) { return; } const messageTimestamp = attachmentProps?.timestamp || attachmentProps?.serverTimestamp || 0; if (attachmentProps?.sender && attachmentProps?.convoId) { void saveAttachmentToDisk({ attachment: attachments[0], messageTimestamp, messageSender: attachmentProps?.sender, conversationId: attachmentProps?.convoId, }); } }, [ attachmentProps?.attachments, attachmentProps?.timestamp, attachmentProps?.serverTimestamp, attachmentProps?.sender, attachmentProps?.convoId, ] ); if (!attachmentProps) { return null; } const { attachments, direction, isTrustedForAttachmentDownload } = attachmentProps; if (!attachments || !attachments[0]) { return null; } const firstAttachment = attachments[0]; const displayImage = canDisplayImage(attachments); if (!isTrustedForAttachmentDownload) { return <ClickToTrustSender messageId={messageId} />; } if ( displayImage && !imageBroken && ((isImage(attachments) && hasImage(attachments)) || (isVideo(attachments) && hasVideoScreenshot(attachments))) ) { return ( <StyledAttachmentContainer messageDirection={direction}> <ImageGrid attachments={attachments} onError={handleImageError} onClickAttachment={onClickOnImageGrid} /> </StyledAttachmentContainer> ); } if (!firstAttachment.pending && !firstAttachment.error && isAudio(attachments)) { return ( <div role="main" onClick={(e: any) => { if (multiSelectMode) { dispatch(toggleSelectedMessageId(messageId)); } e.stopPropagation(); e.preventDefault(); }} style={{ padding: 'var(--margins-xs) 0px' }} > <AudioPlayerWithEncryptedFile src={firstAttachment.url} contentType={firstAttachment.contentType} messageId={messageId} /> </div> ); } const { pending, fileName, fileSize, contentType } = firstAttachment; const extension = getExtensionForDisplay({ contentType, fileName }); return ( <div className="module-message__generic-attachment"> {pending ? ( <div className="module-message__generic-attachment__spinner-container"> <Spinner size="small" /> </div> ) : ( <div className="module-message__generic-attachment__icon-container"> <div role="button" className="module-message__generic-attachment__icon" onClick={onClickOnGenericAttachment} > {extension ? ( <div className="module-message__generic-attachment__icon__extension">{extension}</div> ) : null} </div> </div> )} <div className="module-message__generic-attachment__text"> <div className={classNames( 'module-message__generic-attachment__file-name', `module-message__generic-attachment__file-name--${direction}` )} > {fileName} </div> <div className={classNames( 'module-message__generic-attachment__file-size', `module-message__generic-attachment__file-size--${direction}` )} > {fileSize} </div> </div> </div> ); }; function attachmentIsAttachmentTypeWithPath(attac: any): attac is AttachmentTypeWithPath { return attac.path !== undefined; } const onClickAttachment = async (onClickProps: { attachment: AttachmentTypeWithPath | AttachmentType; messageId: string; }) => { let index = -1; const found = await Data.getMessageById(onClickProps.messageId); if (!found) { window.log.warn('Such message not found'); return; } const msgAttachments = found.getPropsForMessage().attachments; const media = (msgAttachments || []).map(attachmentForMedia => { index++; const messageTimestamp = found.get('timestamp') || found.get('serverTimestamp') || found.get('received_at'); return { index: clone(index), objectURL: attachmentForMedia.url || undefined, contentType: attachmentForMedia.contentType, attachment: attachmentForMedia, messageSender: found.getSource(), messageTimestamp, messageId: onClickProps.messageId, }; }); if (attachmentIsAttachmentTypeWithPath(onClickProps.attachment)) { const lightBoxOptions: LightBoxOptions = { media: media as any, attachment: onClickProps.attachment, }; window.inboxStore?.dispatch(showLightBox(lightBoxOptions)); } else { window.log.warn('Attachment is not of the right type'); } };