import React from 'react'; import classnames from 'classnames'; import * as MIME from '../../../ts/types/MIME'; import * as GoogleChrome from '../../../ts/util/GoogleChrome'; interface Props { attachments: Array; authorColor: string; authorProfileName?: string; authorTitle: string; i18n: (key: string, values?: Array) => string; isFromMe: string; isIncoming: boolean; onClick?: () => void; onClose?: () => void; text: string; } interface QuotedAttachment { contentType: MIME.MIMEType; fileName: string; /* Not included in protobuf */ isVoiceMessage: boolean; thumbnail?: Attachment; } interface Attachment { contentType: MIME.MIMEType; /* Not included in protobuf, and is loaded asynchronously */ objectUrl?: string; } function validateQuote(quote: Props): boolean { if (quote.text) { return true; } if (quote.attachments && quote.attachments.length > 0) { return true; } return false; } function getObjectUrl(thumbnail: Attachment | undefined): string | null { if (thumbnail && thumbnail.objectUrl) { return thumbnail.objectUrl; } return null; } export class Quote extends React.Component { public renderImage(url: string, icon?: string) { const iconElement = icon ? (
) : null; return (
{iconElement}
); } public renderIcon(icon: string) { const { authorColor, isIncoming } = this.props; const backgroundColor = isIncoming ? 'white' : authorColor; const iconColor = isIncoming ? authorColor : 'white'; return (
); } public renderIconContainer() { const { attachments } = this.props; if (!attachments || attachments.length === 0) { return null; } const first = attachments[0]; const { contentType, thumbnail } = first; const objectUrl = getObjectUrl(thumbnail); if (GoogleChrome.isVideoTypeSupported(contentType)) { return objectUrl ? this.renderImage(objectUrl, 'play') : this.renderIcon('movie'); } if (GoogleChrome.isImageTypeSupported(contentType)) { return objectUrl ? this.renderImage(objectUrl) : this.renderIcon('image'); } if (MIME.isAudio(contentType)) { return this.renderIcon('microphone'); } return this.renderIcon('file'); } public renderText() { const { i18n, text, attachments } = this.props; if (text) { return (
); } if (!attachments || attachments.length === 0) { return null; } const first = attachments[0]; const { contentType, fileName, isVoiceMessage } = first; if (GoogleChrome.isVideoTypeSupported(contentType)) { return
{i18n('video')}
; } if (GoogleChrome.isImageTypeSupported(contentType)) { return
{i18n('photo')}
; } if (MIME.isAudio(contentType) && isVoiceMessage) { return
{i18n('voiceMessage')}
; } if (MIME.isAudio(contentType)) { return
{i18n('audio')}
; } return
{fileName}
; } public renderIOSLabel() { const { i18n, isIncoming, isFromMe, authorTitle, authorProfileName, } = this.props; const profileString = authorProfileName ? ` ~${authorProfileName}` : ''; const authorName = `${authorTitle}${profileString}`; const label = isFromMe ? isIncoming ? i18n('replyingToYou') : i18n('replyingToYourself') : i18n('replyingTo', [authorName]); return
{label}
; } public renderClose() { const { onClose } = this.props; if (!onClose) { return null; } // We don't want the overall click handler for the quote to fire, so we stop // propagation before handing control to the caller's callback. const onClick = (e: React.MouseEvent<{}>): void => { e.stopPropagation(); onClose(); }; // We need the container to give us the flexibility to implement the iOS design. return (
); } public renderAuthor() { const { authorColor, authorProfileName, authorTitle, i18n, isFromMe, } = this.props; const authorProfileElement = authorProfileName ? ( ~{authorProfileName} ) : null; return (
{isFromMe ? ( i18n('you') ) : ( {authorTitle} {authorProfileElement} )}
); } public render() { const { authorColor, onClick, isFromMe } = this.props; if (!validateQuote(this.props)) { return null; } const classes = classnames( authorColor, 'quoted-message', isFromMe ? 'from-me' : null, !onClick ? 'no-click' : null ); return (
{this.renderIOSLabel()} {this.renderAuthor()} {this.renderText()}
{this.renderIconContainer()} {this.renderClose()}
); } }