From f7a52d70d6fd917c8d503f39de1ed94738bfce62 Mon Sep 17 00:00:00 2001 From: or-else Date: Sat, 18 Jan 2020 11:15:37 +0300 Subject: [PATCH] first pass at file attachment previews --- src/views/messages-view.jsx | 60 ++++++++++++++++++++------------ src/widgets/doc-preview.jsx | 65 +++++++++++++++++++++++++++++++++++ src/widgets/image-preview.jsx | 2 +- 3 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 src/widgets/doc-preview.jsx diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx index dc77ee68..d0e05d1e 100644 --- a/src/views/messages-view.jsx +++ b/src/views/messages-view.jsx @@ -5,6 +5,7 @@ import Tinode from 'tinode-sdk'; const Drafty = Tinode.Drafty; import ChatMessage from '../widgets/chat-message.jsx'; +import DocPreview from '../widgets/doc-preview.jsx'; import ErrorPanel from '../widgets/error-panel.jsx'; import GroupSubs from '../widgets/group-subs.jsx'; import ImagePreview from '../widgets/image-preview.jsx'; @@ -73,9 +74,10 @@ class MessagesView extends React.Component { this.handleNewMessage = this.handleNewMessage.bind(this); this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this); this.handleInfoReceipt = this.handleInfoReceipt.bind(this); - this.handlePreImagePreview = this.handlePreImagePreview.bind(this); - this.handlePostImagePreview = this.handlePostImagePreview.bind(this); - this.handleCloseImagePreview = this.handleCloseImagePreview.bind(this); + this.handleDocPreview = this.handleDocPreview.bind(this); + this.handleImagePreview = this.handleImagePreview.bind(this); + this.handleImagePostview = this.handleImagePostview.bind(this); + this.handleClosePreview = this.handleClosePreview.bind(this); this.handleFormResponse = this.handleFormResponse.bind(this); this.handleContextClick = this.handleContextClick.bind(this); this.handleShowContextMenuMessage = this.handleShowContextMenuMessage.bind(this); @@ -177,8 +179,9 @@ class MessagesView extends React.Component { topic: null, title: '', avatar: null, - imagePrePreview: null, - imagePostPreview: null, + docPreview: null, + imagePreview: null, + imagePostview: null, typingIndicator: false, scrollPosition: 0, fetchingMessages: false, @@ -188,8 +191,9 @@ class MessagesView extends React.Component { const topic = nextProps.tinode.getTopic(nextProps.topic); nextState = { topic: nextProps.topic, - imagePrePreview: null, - imagePostPreview: null, + docPreview: null, + imagePreview: null, + imagePostview: null, typingIndicator: false, scrollPosition: 0, fetchingMessages: false @@ -442,12 +446,12 @@ class MessagesView extends React.Component { } } - handlePostImagePreview(content) { - this.setState({ imagePostPreview: content }); + handleImagePostview(content) { + this.setState({ imagePostview: content }); } - handleCloseImagePreview() { - this.setState({ imagePostPreview: null, imagePrePreview: null }); + handleClosePreview() { + this.setState({ imagePostview: null, imagePreview: null, docPreview: null }); } handleFormResponse(action, text, data) { @@ -520,6 +524,10 @@ class MessagesView extends React.Component { } } + handleDocPreview(content) { + this.setState({ docPreview: content }); + } + handleAttachFile(file) { if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) { // Too large. @@ -554,8 +562,8 @@ class MessagesView extends React.Component { this.props.sendMessage(msg); } - handlePreImagePreview(content) { - this.setState({ imagePrePreview: content }); + handleImagePreview(content) { + this.setState({ imagePreview: content }); } handleAttachImage(file) { @@ -565,7 +573,7 @@ class MessagesView extends React.Component { imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false, // Success (bits, mime, width, height, fname) => { - this.handlePreImagePreview({ + this.handleImagePreview({ url: URL.createObjectURL(file), bits: bits, filename: fname, @@ -584,7 +592,7 @@ class MessagesView extends React.Component { imageFileToBase64(file, // Success (bits, mime, width, height, fname) => { - this.handlePreImagePreview({ + this.handleImagePreview({ url: URL.createObjectURL(file), bits: bits, filename: fname, @@ -616,20 +624,28 @@ class MessagesView extends React.Component { ); } else { let component2; - if (this.state.imagePrePreview) { + if (this.state.imagePreview) { // Preview image before sending. component2 = ( ); - } else if (this.state.imagePostPreview) { + } else if (this.state.imagePostview) { // Expand received image. component2 = ( + content={this.state.imagePostview} + onClose={this.handleClosePreview} /> + ); + } else if (this.state.docPreview) { + // Preview attachment before sending. + component2 = ( + ); } else { const topic = this.props.tinode.getTopic(this.state.topic); @@ -683,7 +699,7 @@ class MessagesView extends React.Component { sequence={sequence} received={deliveryStatus} uploader={msg._uploader} viewportWidth={this.props.viewportWidth} showContextMenu={this.handleShowContextMenuMessage} - onImagePreview={this.handlePostImagePreview} + onImagePreview={this.handleImagePostview} onFormResponse={this.handleFormResponse} onError={this.props.onError} key={msg.seq} /> diff --git a/src/widgets/doc-preview.jsx b/src/widgets/doc-preview.jsx new file mode 100644 index 00000000..4b541040 --- /dev/null +++ b/src/widgets/doc-preview.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import SendMessage from '../widgets/send-message.jsx'; + +import { bytesToHumanSize } from '../lib/strformat.js'; + +// Get material icon name from mime type. +function iconFromMime(mime) { + // If more icons become available in material icons, add them to this mime-to-icon mapping. + const mimeToIcon = {default: 'insert_drive_file', image: 'image', text: 'description', video: 'theaters'}; + + return mimeToIcon[mime] || mimeToIcon[(mime || '').split('/')[0]] || mimeToIcon['default']; +} + +export default class DocPreview extends React.PureComponent { + constructor(props) { + super(props); + + this.handleSendDoc = this.handleSendDoc.bind(this); + } + + handleSendDoc(caption) { + this.props.onClose(); + this.props.onSendMessage(caption, this.props.content.type, this.props.content.bits, + this.props.content.width, this.props.content.height, this.props.content.filename); + } + + render() { + if (!this.props.content) { + return null; + } + + return ( + const doc = iconFromMime(this.props.content.type); +
+
+ {this.props.content.filename} + {e.preventDefault(); this.props.onClose();}}>close +
+
+ {doc} +
+ + +
+ ); + } +}; diff --git a/src/widgets/image-preview.jsx b/src/widgets/image-preview.jsx index e69a509f..394ef9ed 100644 --- a/src/widgets/image-preview.jsx +++ b/src/widgets/image-preview.jsx @@ -50,7 +50,7 @@ export default class ImagePreview extends React.PureComponent { // Averate font aspect ratio is ~0.5; File name takes 1/3 of the viewport width. const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12); if (filename.length > maxlength) { - filename = filename.slice(0, maxlength/2 - 1) + '...' + filename.slice(1 - maxlength/2); + filename = filename.slice(0, maxlength/2 - 1) + '…' + filename.slice(1 - maxlength/2); } const width = this.props.content.width || '-'; const height = this.props.content.height || '-';