first pass at file attachment previews
This commit is contained in:
+38
-22
@@ -5,6 +5,7 @@ import Tinode from 'tinode-sdk';
|
|||||||
const Drafty = Tinode.Drafty;
|
const Drafty = Tinode.Drafty;
|
||||||
|
|
||||||
import ChatMessage from '../widgets/chat-message.jsx';
|
import ChatMessage from '../widgets/chat-message.jsx';
|
||||||
|
import DocPreview from '../widgets/doc-preview.jsx';
|
||||||
import ErrorPanel from '../widgets/error-panel.jsx';
|
import ErrorPanel from '../widgets/error-panel.jsx';
|
||||||
import GroupSubs from '../widgets/group-subs.jsx';
|
import GroupSubs from '../widgets/group-subs.jsx';
|
||||||
import ImagePreview from '../widgets/image-preview.jsx';
|
import ImagePreview from '../widgets/image-preview.jsx';
|
||||||
@@ -73,9 +74,10 @@ class MessagesView extends React.Component {
|
|||||||
this.handleNewMessage = this.handleNewMessage.bind(this);
|
this.handleNewMessage = this.handleNewMessage.bind(this);
|
||||||
this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);
|
this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);
|
||||||
this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
|
this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
|
||||||
this.handlePreImagePreview = this.handlePreImagePreview.bind(this);
|
this.handleDocPreview = this.handleDocPreview.bind(this);
|
||||||
this.handlePostImagePreview = this.handlePostImagePreview.bind(this);
|
this.handleImagePreview = this.handleImagePreview.bind(this);
|
||||||
this.handleCloseImagePreview = this.handleCloseImagePreview.bind(this);
|
this.handleImagePostview = this.handleImagePostview.bind(this);
|
||||||
|
this.handleClosePreview = this.handleClosePreview.bind(this);
|
||||||
this.handleFormResponse = this.handleFormResponse.bind(this);
|
this.handleFormResponse = this.handleFormResponse.bind(this);
|
||||||
this.handleContextClick = this.handleContextClick.bind(this);
|
this.handleContextClick = this.handleContextClick.bind(this);
|
||||||
this.handleShowContextMenuMessage = this.handleShowContextMenuMessage.bind(this);
|
this.handleShowContextMenuMessage = this.handleShowContextMenuMessage.bind(this);
|
||||||
@@ -177,8 +179,9 @@ class MessagesView extends React.Component {
|
|||||||
topic: null,
|
topic: null,
|
||||||
title: '',
|
title: '',
|
||||||
avatar: null,
|
avatar: null,
|
||||||
imagePrePreview: null,
|
docPreview: null,
|
||||||
imagePostPreview: null,
|
imagePreview: null,
|
||||||
|
imagePostview: null,
|
||||||
typingIndicator: false,
|
typingIndicator: false,
|
||||||
scrollPosition: 0,
|
scrollPosition: 0,
|
||||||
fetchingMessages: false,
|
fetchingMessages: false,
|
||||||
@@ -188,8 +191,9 @@ class MessagesView extends React.Component {
|
|||||||
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
||||||
nextState = {
|
nextState = {
|
||||||
topic: nextProps.topic,
|
topic: nextProps.topic,
|
||||||
imagePrePreview: null,
|
docPreview: null,
|
||||||
imagePostPreview: null,
|
imagePreview: null,
|
||||||
|
imagePostview: null,
|
||||||
typingIndicator: false,
|
typingIndicator: false,
|
||||||
scrollPosition: 0,
|
scrollPosition: 0,
|
||||||
fetchingMessages: false
|
fetchingMessages: false
|
||||||
@@ -442,12 +446,12 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostImagePreview(content) {
|
handleImagePostview(content) {
|
||||||
this.setState({ imagePostPreview: content });
|
this.setState({ imagePostview: content });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCloseImagePreview() {
|
handleClosePreview() {
|
||||||
this.setState({ imagePostPreview: null, imagePrePreview: null });
|
this.setState({ imagePostview: null, imagePreview: null, docPreview: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFormResponse(action, text, data) {
|
handleFormResponse(action, text, data) {
|
||||||
@@ -520,6 +524,10 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDocPreview(content) {
|
||||||
|
this.setState({ docPreview: content });
|
||||||
|
}
|
||||||
|
|
||||||
handleAttachFile(file) {
|
handleAttachFile(file) {
|
||||||
if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {
|
if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {
|
||||||
// Too large.
|
// Too large.
|
||||||
@@ -554,8 +562,8 @@ class MessagesView extends React.Component {
|
|||||||
this.props.sendMessage(msg);
|
this.props.sendMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreImagePreview(content) {
|
handleImagePreview(content) {
|
||||||
this.setState({ imagePrePreview: content });
|
this.setState({ imagePreview: content });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAttachImage(file) {
|
handleAttachImage(file) {
|
||||||
@@ -565,7 +573,7 @@ class MessagesView extends React.Component {
|
|||||||
imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false,
|
imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false,
|
||||||
// Success
|
// Success
|
||||||
(bits, mime, width, height, fname) => {
|
(bits, mime, width, height, fname) => {
|
||||||
this.handlePreImagePreview({
|
this.handleImagePreview({
|
||||||
url: URL.createObjectURL(file),
|
url: URL.createObjectURL(file),
|
||||||
bits: bits,
|
bits: bits,
|
||||||
filename: fname,
|
filename: fname,
|
||||||
@@ -584,7 +592,7 @@ class MessagesView extends React.Component {
|
|||||||
imageFileToBase64(file,
|
imageFileToBase64(file,
|
||||||
// Success
|
// Success
|
||||||
(bits, mime, width, height, fname) => {
|
(bits, mime, width, height, fname) => {
|
||||||
this.handlePreImagePreview({
|
this.handleImagePreview({
|
||||||
url: URL.createObjectURL(file),
|
url: URL.createObjectURL(file),
|
||||||
bits: bits,
|
bits: bits,
|
||||||
filename: fname,
|
filename: fname,
|
||||||
@@ -616,20 +624,28 @@ class MessagesView extends React.Component {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let component2;
|
let component2;
|
||||||
if (this.state.imagePrePreview) {
|
if (this.state.imagePreview) {
|
||||||
// Preview image before sending.
|
// Preview image before sending.
|
||||||
component2 = (
|
component2 = (
|
||||||
<ImagePreview
|
<ImagePreview
|
||||||
content={this.state.imagePrePreview}
|
content={this.state.imagePreview}
|
||||||
onClose={this.handleCloseImagePreview}
|
onClose={this.handleClosePreview}
|
||||||
onSendMessage={this.sendImageAttachment} />
|
onSendMessage={this.sendImageAttachment} />
|
||||||
);
|
);
|
||||||
} else if (this.state.imagePostPreview) {
|
} else if (this.state.imagePostview) {
|
||||||
// Expand received image.
|
// Expand received image.
|
||||||
component2 = (
|
component2 = (
|
||||||
<ImagePreview
|
<ImagePreview
|
||||||
content={this.state.imagePostPreview}
|
content={this.state.imagePostview}
|
||||||
onClose={this.handleCloseImagePreview} />
|
onClose={this.handleClosePreview} />
|
||||||
|
);
|
||||||
|
} else if (this.state.docPreview) {
|
||||||
|
// Preview attachment before sending.
|
||||||
|
component2 = (
|
||||||
|
<DocPreview
|
||||||
|
content={this.state.docPreview}
|
||||||
|
onClose={this.handleClosePreview}
|
||||||
|
onSendMessage={this.sendFileAttachment} />
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
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}
|
sequence={sequence} received={deliveryStatus} uploader={msg._uploader}
|
||||||
viewportWidth={this.props.viewportWidth}
|
viewportWidth={this.props.viewportWidth}
|
||||||
showContextMenu={this.handleShowContextMenuMessage}
|
showContextMenu={this.handleShowContextMenuMessage}
|
||||||
onImagePreview={this.handlePostImagePreview}
|
onImagePreview={this.handleImagePostview}
|
||||||
onFormResponse={this.handleFormResponse}
|
onFormResponse={this.handleFormResponse}
|
||||||
onError={this.props.onError}
|
onError={this.props.onError}
|
||||||
key={msg.seq} />
|
key={msg.seq} />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
<div id="image-preview">
|
||||||
|
<div id="image-preview-caption-panel">
|
||||||
|
<span>{this.props.content.filename}</span>
|
||||||
|
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
|
||||||
|
</div>
|
||||||
|
<div id="image-preview-container">
|
||||||
|
<i className="material-icons gray">{doc}</i>
|
||||||
|
</div>
|
||||||
|
<div id="image-preview-footer">
|
||||||
|
<div>
|
||||||
|
<div><b><FormattedMessage id="label_content_type" defaultMessage="Content type:"
|
||||||
|
description="Label for file content type (mime)" /></b></div>
|
||||||
|
<div>{this.props.content.type}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div><b><FormattedMessage id="label_size" defaultMessage="Size:"
|
||||||
|
description="Label for file size" /></b></div>
|
||||||
|
<div>{bytesToHumanSize(this.props.content.size)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SendMessage
|
||||||
|
tinode={this.props.tinode}
|
||||||
|
topic={this.props.topic}
|
||||||
|
messagePrompt="add_image_caption"
|
||||||
|
acceptBlank={true}
|
||||||
|
onSendMessage={this.handleSendImage}
|
||||||
|
onError={this.props.onError} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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.
|
// 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);
|
const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12);
|
||||||
if (filename.length > maxlength) {
|
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 width = this.props.content.width || '-';
|
||||||
const height = this.props.content.height || '-';
|
const height = this.props.content.height || '-';
|
||||||
|
|||||||
Reference in New Issue
Block a user