previewing image before sending works now

This commit is contained in:
or-else
2020-01-14 18:07:56 +03:00
parent 90889454a0
commit 72d978ed55
13 changed files with 308 additions and 146 deletions
+1
View File
@@ -146,6 +146,7 @@
"search_placeholder": "List like email:alice@example.com, tel:17025550003...", "search_placeholder": "List like email:alice@example.com, tel:17025550003...",
"messaging_disabled_prompt": "Messaging disabled", "messaging_disabled_prompt": "Messaging disabled",
"new_message_prompt": "New message", "new_message_prompt": "New message",
"image_caption_prompt": "Image caption",
"file_attachment_too_large": "The file size {size} exceeds the {limit} limit.", "file_attachment_too_large": "The file size {size} exceeds the {limit} limit.",
"cannot_initiate_file_upload": "Cannot initiate file upload.", "cannot_initiate_file_upload": "Cannot initiate file upload.",
"tags_not_found": "No tags defined. Add some.", "tags_not_found": "No tags defined. Add some.",
+2 -1
View File
@@ -150,5 +150,6 @@
"topic_delete_warning": "Вы действительно хотите удалить этот чат?", "topic_delete_warning": "Вы действительно хотите удалить этот чат?",
"topic_block_warning": "Вы действительно хотите заблокировать этот чат?", "topic_block_warning": "Вы действительно хотите заблокировать этот чат?",
"search_no_results": "Ничего не найдено", "search_no_results": "Ничего не найдено",
"deleted_content": "удалено" "deleted_content": "удалено",
"image_caption_prompt": "Подпись к фото"
} }
+2 -1
View File
@@ -150,5 +150,6 @@
"topic_delete_warning": ":!![MISSING]!!:Are you sure you want to delete this conversation?", "topic_delete_warning": ":!![MISSING]!!:Are you sure you want to delete this conversation?",
"topic_block_warning": ":!![MISSING]!!:Are you sure you want to block this conversation?", "topic_block_warning": ":!![MISSING]!!:Are you sure you want to block this conversation?",
"search_no_results": ":!![MISSING]!!:Search returned no results", "search_no_results": ":!![MISSING]!!:Search returned no results",
"deleted_content": "内容已删除" "deleted_content": "内容已删除",
"image_caption_prompt": "图片标题"
} }
+5 -2
View File
@@ -147,6 +147,7 @@
"search_placeholder": "List like email:alice@example.com, tel:17025550003...", "search_placeholder": "List like email:alice@example.com, tel:17025550003...",
"messaging_disabled_prompt": "Messaging disabled", "messaging_disabled_prompt": "Messaging disabled",
"new_message_prompt": "New message", "new_message_prompt": "New message",
"image_caption_prompt": "Image caption",
"file_attachment_too_large": "The file size {size} exceeds the {limit} limit.", "file_attachment_too_large": "The file size {size} exceeds the {limit} limit.",
"cannot_initiate_file_upload": "Cannot initiate file upload.", "cannot_initiate_file_upload": "Cannot initiate file upload.",
"tags_not_found": "No tags defined. Add some.", "tags_not_found": "No tags defined. Add some.",
@@ -305,7 +306,8 @@
"topic_delete_warning": "Вы действительно хотите удалить этот чат?", "topic_delete_warning": "Вы действительно хотите удалить этот чат?",
"topic_block_warning": "Вы действительно хотите заблокировать этот чат?", "topic_block_warning": "Вы действительно хотите заблокировать этот чат?",
"search_no_results": "Ничего не найдено", "search_no_results": "Ничего не найдено",
"deleted_content": "удалено" "deleted_content": "удалено",
"image_caption_prompt": "Подпись к фото"
}, },
"zh": { "zh": {
"archived_contacts": "已归档联系人 ({count})", "archived_contacts": "已归档联系人 ({count})",
@@ -450,6 +452,7 @@
"action_delete_messages": "删除所有帖子", "action_delete_messages": "删除所有帖子",
"action_block_contact": "屏蔽联系人", "action_block_contact": "屏蔽联系人",
"action_report_chat": "检举垃圾邮件", "action_report_chat": "检举垃圾邮件",
"deleted_content": "内容已删除" "deleted_content": "内容已删除",
"image_caption_prompt": "图片标题"
} }
} }
+78 -21
View File
@@ -14,9 +14,11 @@ import LoadSpinner from '../widgets/load-spinner.jsx';
import LogoView from './logo-view.jsx'; import LogoView from './logo-view.jsx';
import SendMessage from '../widgets/send-message.jsx'; import SendMessage from '../widgets/send-message.jsx';
import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js'; import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE,
import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64, imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js'; MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
import { shortDateFormat } from '../lib/strformat.js'; import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64,
imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';
import { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';
const messages = defineMessages({ const messages = defineMessages({
online_now: { online_now: {
@@ -61,6 +63,9 @@ class MessagesView extends React.Component {
// this.propsChange = this.propsChange.bind(this); // this.propsChange = this.propsChange.bind(this);
this.leave = this.leave.bind(this); this.leave = this.leave.bind(this);
this.sendImageAttachment = this.sendImageAttachment.bind(this);
this.sendFileAttachment = this.sendFileAttachment.bind(this);
this.sendKeyPress = this.sendKeyPress.bind(this);
this.handleScrollReference = this.handleScrollReference.bind(this); this.handleScrollReference = this.handleScrollReference.bind(this);
this.handleScrollEvent = this.handleScrollEvent.bind(this); this.handleScrollEvent = this.handleScrollEvent.bind(this);
this.handleDescChange = this.handleDescChange.bind(this); this.handleDescChange = this.handleDescChange.bind(this);
@@ -68,7 +73,8 @@ 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.handleImagePreview = this.handleImagePreview.bind(this); this.handlePreImagePreview = this.handlePreImagePreview.bind(this);
this.handlePostImagePreview = this.handlePostImagePreview.bind(this);
this.handleCloseImagePreview = this.handleCloseImagePreview.bind(this); this.handleCloseImagePreview = this.handleCloseImagePreview.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);
@@ -77,7 +83,6 @@ class MessagesView extends React.Component {
this.handleEnablePeer = this.handleEnablePeer.bind(this); this.handleEnablePeer = this.handleEnablePeer.bind(this);
this.handleAttachFile = this.handleAttachFile.bind(this); this.handleAttachFile = this.handleAttachFile.bind(this);
this.handleAttachImage = this.handleAttachImage.bind(this); this.handleAttachImage = this.handleAttachImage.bind(this);
this.handleNewImagePreview = this.handleNewImagePreview.bind(this);
} }
componentDidMount() { componentDidMount() {
@@ -172,7 +177,8 @@ class MessagesView extends React.Component {
topic: null, topic: null,
title: '', title: '',
avatar: null, avatar: null,
imagePreview: null, imagePrePreview: null,
imagePostPreview: null,
typingIndicator: false, typingIndicator: false,
scrollPosition: 0, scrollPosition: 0,
fetchingMessages: false, fetchingMessages: false,
@@ -182,7 +188,8 @@ 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,
imagePreview: null, imagePrePreview: null,
imagePostPreview: null,
typingIndicator: false, typingIndicator: false,
scrollPosition: 0, scrollPosition: 0,
fetchingMessages: false fetchingMessages: false
@@ -435,12 +442,12 @@ class MessagesView extends React.Component {
} }
} }
handleImagePreview(content) { handlePostImagePreview(content) {
this.setState({ imagePreview: content }); this.setState({ imagePostPreview: content });
} }
handleCloseImagePreview() { handleCloseImagePreview() {
this.setState({ imagePreview: null }); this.setState({ imagePostPreview: null, imagePrePreview: null });
} }
handleFormResponse(action, text, data) { handleFormResponse(action, text, data) {
@@ -495,7 +502,14 @@ class MessagesView extends React.Component {
this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic); this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic);
} }
handleAttachFile(mime, bits, fname, size, uploadCompletionPromise, uploader) { sendKeyPress() {
const topic = this.props.tinode.getTopic(this.state.topic);
if (topic.isSubscribed()) {
topic.noteKeyPress();
}
}
sendFileAttachment(mime, bits, fname, size, uploadCompletionPromise, uploader) {
if (uploadCompletionPromise) { if (uploadCompletionPromise) {
// Format data and initiate upload. // Format data and initiate upload.
const msg = Drafty.attachFile(null, mime, null, fname, size, uploadCompletionPromise); const msg = Drafty.attachFile(null, mime, null, fname, size, uploadCompletionPromise);
@@ -506,9 +520,42 @@ class MessagesView extends React.Component {
} }
} }
handleNewImagePreview(content) { handleAttachFile(file) {
this.setState({ imagePreview: content }); if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {
// this.props.sendMessage(Drafty.insertImage(null, 0, mime, bits, width, height, fname)); // Too large.
this.props.onError(formatMessage(messages.file_attachment_too_large,
{size: bytesToHumanSize(file.size), limit: bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE)}), 'err');
} else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) {
// Too large to send inband - uploading out of band and sending as a link.
const uploader = this.props.tinode.getLargeFileHelper();
if (!uploader) {
this.props.onError(formatMessage(messages.cannot_initiate_upload));
return;
}
// Pass data and the uploader to the TinodeWeb.
this.sendFileAttachment(file.type, null, file.name, file.size, uploadCompletionPromise, uploader);
} else {
// Small enough to send inband.
fileToBase64(file,
(mime, bits, fname) => {
this.sendFileAttachment(mime, bits, fname);
},
this.props.onError
);
}
}
sendImageAttachment(caption, mime, bits, width, height, fname) {
let msg = Drafty.insertImage(null, 0, mime, bits, width, height, fname);
if (caption) {
msg = Drafty.appendLineBreak(msg);
msg = Drafty.append(msg, Drafty.init(caption));
}
this.props.sendMessage(msg);
}
handlePreImagePreview(content) {
this.setState({ imagePrePreview: content });
} }
handleAttachImage(file) { handleAttachImage(file) {
@@ -518,8 +565,9 @@ 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.handleNewImagePreview({ this.handlePreImagePreview({
url: URL.createObjectURL(file), url: URL.createObjectURL(file),
bits: bits,
filename: fname, filename: fname,
width: width, width: width,
height: height, height: height,
@@ -536,8 +584,9 @@ class MessagesView extends React.Component {
imageFileToBase64(file, imageFileToBase64(file,
// Success // Success
(bits, mime, width, height, fname) => { (bits, mime, width, height, fname) => {
this.handleNewImagePreview({ this.handlePreImagePreview({
url: URL.createObjectURL(file), url: URL.createObjectURL(file),
bits: bits,
filename: fname, filename: fname,
width: width, width: width,
height: height, height: height,
@@ -567,13 +616,20 @@ class MessagesView extends React.Component {
); );
} else { } else {
let component2; let component2;
if (this.state.imagePreview) { if (this.state.imagePrePreview) {
// Preview image before sending.
component2 = ( component2 = (
<ImagePreview <ImagePreview
content={this.state.imagePreview} content={this.state.imagePrePreview}
downloadable={true}
onClose={this.handleCloseImagePreview} onClose={this.handleCloseImagePreview}
onSendMessage={this.props.sendMessage} /> onSendMessage={this.sendImageAttachment} />
);
} else if (this.state.imagePostPreview) {
// Expand received image.
component2 = (
<ImagePreview
content={this.state.imagePostPreview}
onClose={this.handleCloseImagePreview} />
); );
} else { } else {
const topic = this.props.tinode.getTopic(this.state.topic); const topic = this.props.tinode.getTopic(this.state.topic);
@@ -627,7 +683,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.handleImagePreview} onImagePreview={this.handlePostImagePreview}
onFormResponse={this.handleFormResponse} onFormResponse={this.handleFormResponse}
onError={this.props.onError} onError={this.props.onError}
key={msg.seq} /> key={msg.seq} />
@@ -723,6 +779,7 @@ class MessagesView extends React.Component {
topic={this.props.topic} topic={this.props.topic}
disabled={!this.state.isWriter} disabled={!this.state.isWriter}
onSendMessage={this.props.sendMessage} onSendMessage={this.props.sendMessage}
onKeyPress={this.sendKeyPress}
onAttachFile={this.handleAttachFile} onAttachFile={this.handleAttachFile}
onAttachImage={this.handleAttachImage} onAttachImage={this.handleAttachImage}
onError={this.props.onError} />} onError={this.props.onError} />}
+15 -4
View File
@@ -14,6 +14,8 @@ export default class ImagePreview extends React.PureComponent {
width: 0, width: 0,
height: 0 height: 0
}; };
this.handleSendImage = this.handleSendImage.bind(this);
} }
assignWidth(node) { assignWidth(node) {
@@ -26,6 +28,12 @@ export default class ImagePreview extends React.PureComponent {
} }
} }
handleSendImage(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() { render() {
if (!this.props.content) { if (!this.props.content) {
return null; return null;
@@ -47,15 +55,16 @@ export default class ImagePreview extends React.PureComponent {
const width = this.props.content.width || '-'; const width = this.props.content.width || '-';
const height = this.props.content.height || '-'; const height = this.props.content.height || '-';
return ( return (
<div id="image-preview" onClick={this.props.onClose}> <div id="image-preview">
<div id="image-preview-caption-panel"> <div id="image-preview-caption-panel">
{this.props.downloadable ? {!this.props.onSendMessage ?
<a href={this.props.content.url} download={this.props.content.filename}> <a href={this.props.content.url} download={this.props.content.filename}>
<i className="material-icons">file_download</i> <FormattedMessage <i className="material-icons">file_download</i> <FormattedMessage
id="download_action" defaultMessage="download" description="Call to action [download]" /> id="download_action" defaultMessage="download" description="Call to action [download]" />
</a> </a>
: :
null} <span>{this.props.content.filename}</span>
}
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a> <a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
</div> </div>
<div id="image-preview-container" ref={(node) => this.assignWidth(node)}> <div id="image-preview-container" ref={(node) => this.assignWidth(node)}>
@@ -65,7 +74,9 @@ export default class ImagePreview extends React.PureComponent {
<SendMessage <SendMessage
tinode={this.props.tinode} tinode={this.props.tinode}
topic={this.props.topic} topic={this.props.topic}
onSendMessage={this.props.onSendMessage} messagePrompt="add_image_caption"
acceptBlank={true}
onSendMessage={this.handleSendImage}
onError={this.props.onError} /> onError={this.props.onError} />
: :
<div id="image-preview-footer"> <div id="image-preview-footer">
+19 -36
View File
@@ -3,9 +3,8 @@ import React from 'react';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import { Drafty } from 'tinode-sdk'; import { Drafty } from 'tinode-sdk';
import { KEYPRESS_DELAY, MAX_EXTERN_ATTACHMENT_SIZE, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js'; import { KEYPRESS_DELAY } from '../config.js';
import { filePasted, fileToBase64 } from '../lib/blob-helpers.js'; import { filePasted } from '../lib/blob-helpers.js';
import { bytesToHumanSize } from '../lib/strformat.js';
const messages = defineMessages({ const messages = defineMessages({
'messaging_disabled': { 'messaging_disabled': {
@@ -16,7 +15,12 @@ const messages = defineMessages({
'type_new_message': { 'type_new_message': {
id: 'new_message_prompt', id: 'new_message_prompt',
defaultMessage: 'New message', defaultMessage: 'New message',
description: 'Prompt in SendMessage in read-only topic' description: 'Prompt in send message field'
},
'add_image_caption': {
id: 'image_caption_prompt',
defaultMessage: 'Image caption',
description: 'Prompt in SendMessage for attached image'
}, },
'file_attachment_too_large': { 'file_attachment_too_large': {
id: 'file_attachment_too_large', id: 'file_attachment_too_large',
@@ -90,29 +94,7 @@ class SendMessage extends React.PureComponent {
handleAttachFile(e) { handleAttachFile(e) {
const {formatMessage} = this.props.intl; const {formatMessage} = this.props.intl;
if (e.target.files && e.target.files.length > 0) { if (e.target.files && e.target.files.length > 0) {
const file = e.target.files[0]; this.props.onAttachFile(e.target.files[0]);
if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {
// Too large.
this.props.onError(formatMessage(messages.file_attachment_too_large,
{size: bytesToHumanSize(file.size), limit: bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE)}), 'err');
} else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) {
// Too large to send inband - uploading out of band and sending as a link.
const uploader = this.props.tinode.getLargeFileHelper();
if (!uploader) {
this.props.onError(formatMessage(messages.cannot_initiate_upload));
return;
}
// Pass data and the uploader to the TinodeWeb.
this.props.onAttachFile(file.type, null, file.name, file.size, uploadCompletionPromise, uploader);
} else {
// Small enough to send inband.
fileToBase64(file,
(mime, bits, fname) => {
this.props.onAttachFile(mime, bits, fname);
},
this.props.onError
);
}
} }
// Clear the value so the same file can be uploaded again. // Clear the value so the same file can be uploaded again.
e.target.value = ''; e.target.value = '';
@@ -121,8 +103,8 @@ class SendMessage extends React.PureComponent {
handleSend(e) { handleSend(e) {
e.preventDefault(); e.preventDefault();
const message = this.state.message.trim(); const message = this.state.message.trim();
if (message) { if (message || this.props.acceptBlank) {
this.props.onSendMessage(this.state.message.trim()); this.props.onSendMessage(message);
this.setState({message: ''}); this.setState({message: ''});
} }
} }
@@ -143,13 +125,12 @@ class SendMessage extends React.PureComponent {
handleMessageTyping(e) { handleMessageTyping(e) {
const newState = {message: e.target.value}; const newState = {message: e.target.value};
const now = new Date().getTime(); if (this.props.onKeyPress) {
if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) { const now = new Date().getTime();
const topic = this.props.tinode.getTopic(this.props.topic); if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) {
if (topic.isSubscribed()) { this.props.onKeyPress();
topic.noteKeyPress(); newState.keypressTimestamp = now;
} }
newState.keypressTimestamp = now;
} }
this.setState(newState); this.setState(newState);
} }
@@ -158,7 +139,9 @@ class SendMessage extends React.PureComponent {
const {formatMessage} = this.props.intl; const {formatMessage} = this.props.intl;
const prompt = this.props.disabled ? const prompt = this.props.disabled ?
formatMessage(messages.messaging_disabled) : formatMessage(messages.messaging_disabled) :
formatMessage(messages.type_new_message); (this.props.messagePrompt ?
formatMessage(messages[this.props.messagePrompt]) :
formatMessage(messages.type_new_message));
return ( return (
<div id="send-message-panel"> <div id="send-message-panel">
{this.props.onAttachFile ? {this.props.onAttachFile ?
+119 -74
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+63 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long