|  |  |  | 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'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; |