multiple simultaneous uploads and downloads work now
This commit is contained in:
@@ -86,10 +86,10 @@ function bytesToHumanSize(bytes) {
|
|||||||
return '0 Bytes';
|
return '0 Bytes';
|
||||||
}
|
}
|
||||||
|
|
||||||
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||||
var bucket = Math.floor(Math.log2(bytes) / 10) | 0;
|
var bucket = Math.min(Math.floor(Math.log2(bytes) / 10) | 0, sizes.length-1);
|
||||||
var count = bytes / Math.pow(1024, bucket);
|
var count = bytes / Math.pow(1024, bucket);
|
||||||
var round = bucket > 0 ? (count < 10 ? 2 : (count < 100 ? 1 : 0)) : 0;
|
var round = bucket > 0 ? (count < 3 ? 2 : (count < 30 ? 1 : 0)) : 0;
|
||||||
return count.toFixed(round) + ' ' + sizes[bucket];
|
return count.toFixed(round) + ' ' + sizes[bucket];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,26 +673,6 @@ class LetterTile extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
/* END Lettertile */
|
/* END Lettertile */
|
||||||
|
|
||||||
/* File uload spinner with a cancel inside */
|
|
||||||
class FileUploadProgress extends React.PureComponent {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="uploader">
|
|
||||||
<div><span style={{width: (this.props.progress * 100) + "%"}}></span></div>
|
|
||||||
{this.props.progress < 0.999 ?
|
|
||||||
<a href="javascript:;" onClick={this.props.onCancel}><i className="material-icons">close</i> cancel</a>
|
|
||||||
:
|
|
||||||
<span>finishing...</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BEGIN In-place text editor. Shows text with an icon
|
/* BEGIN In-place text editor. Shows text with an icon
|
||||||
* which toggles it into an input field */
|
* which toggles it into an input field */
|
||||||
class InPlaceEdit extends React.Component {
|
class InPlaceEdit extends React.Component {
|
||||||
@@ -3320,8 +3300,17 @@ class ChatMessage extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
progress: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.uploader) {
|
||||||
|
props.uploader.onProgress = this.handleProgress.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
this.handlePreviewImage = this.handlePreviewImage.bind(this);
|
this.handlePreviewImage = this.handlePreviewImage.bind(this);
|
||||||
this.handleContextClick = this.handleContextClick.bind(this);
|
this.handleContextClick = this.handleContextClick.bind(this);
|
||||||
|
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewImage(e) {
|
handlePreviewImage(e) {
|
||||||
@@ -3342,6 +3331,14 @@ class ChatMessage extends React.Component {
|
|||||||
this.props.showContextMenu({ seq: this.props.seq, y: e.pageY, x: e.pageX });
|
this.props.showContextMenu({ seq: this.props.seq, y: e.pageY, x: e.pageX });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleProgress(ratio) {
|
||||||
|
this.setState({progress: ratio});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancelUpload() {
|
||||||
|
this.props.uploader.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var elementKey = 0;
|
var elementKey = 0;
|
||||||
|
|
||||||
@@ -3380,8 +3377,8 @@ class ChatMessage extends React.Component {
|
|||||||
downloadUrl={Drafty.getDownloadUrl(att)}
|
downloadUrl={Drafty.getDownloadUrl(att)}
|
||||||
filename={att.name} uploader={Drafty.isUploading(att)}
|
filename={att.name} uploader={Drafty.isUploading(att)}
|
||||||
mimetype={att.mime} size={Drafty.getEntitySize(att)}
|
mimetype={att.mime} size={Drafty.getEntitySize(att)}
|
||||||
progress={this.props.progress}
|
progress={this.state.progress}
|
||||||
onCancelUpload={this.props.onCancelUpload}
|
onCancelUpload={this.handleCancelUpload}
|
||||||
onError={this.props.onError}
|
onError={this.props.onError}
|
||||||
key={i} />);
|
key={i} />);
|
||||||
}, this);
|
}, this);
|
||||||
@@ -3403,7 +3400,6 @@ class ChatMessage extends React.Component {
|
|||||||
<p>{content}
|
<p>{content}
|
||||||
{attachments}
|
{attachments}
|
||||||
<ReceivedMarker
|
<ReceivedMarker
|
||||||
response={this.props.response}
|
|
||||||
timestamp={this.props.timestamp}
|
timestamp={this.props.timestamp}
|
||||||
received={this.props.received} />
|
received={this.props.received} />
|
||||||
</p>
|
</p>
|
||||||
@@ -3420,37 +3416,6 @@ class ChatMessage extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class MessageUploader extends React.PureComponent {
|
|
||||||
render() {
|
|
||||||
var sideClass = this.props.sequence + " right";
|
|
||||||
var bubbleClass = "bubble tip";
|
|
||||||
|
|
||||||
var attachments = [
|
|
||||||
<Attachment
|
|
||||||
uploader={true} filename={this.props.filename} size={this.props.size}
|
|
||||||
progress={this.props.progress}
|
|
||||||
onCancelUpload={this.props.onCancelUpload}
|
|
||||||
onError={this.props.onError} />
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li className={sideClass}>
|
|
||||||
<div>
|
|
||||||
<div className="bubble tip">
|
|
||||||
<p>{attachments}
|
|
||||||
<ReceivedMarker
|
|
||||||
response={this.props.response}
|
|
||||||
timestamp={this.props.timestamp}
|
|
||||||
received={this.props.received} />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* Received/read indicator */
|
/* Received/read indicator */
|
||||||
class ReceivedMarker extends React.PureComponent {
|
class ReceivedMarker extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
@@ -3477,6 +3442,22 @@ class ReceivedMarker extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* File uload/download progress indicator with a cancel inside */
|
||||||
|
class FileProgress extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="uploader">
|
||||||
|
<div><span style={{width: (this.props.progress * 100) + "%"}}></span></div>
|
||||||
|
{this.props.progress < 0.999 ?
|
||||||
|
<a href="javascript:;" onClick={this.props.onCancel}><i className="material-icons">close</i> cancel</a>
|
||||||
|
:
|
||||||
|
<span>finishing...</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Attachment extends React.Component {
|
class Attachment extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -3493,16 +3474,15 @@ class Attachment extends React.Component {
|
|||||||
downloadFile(url, filename, mimetype) {
|
downloadFile(url, filename, mimetype) {
|
||||||
var downloader = Tinode.getLargeFileHelper();
|
var downloader = Tinode.getLargeFileHelper();
|
||||||
this.setState({downloader: downloader});
|
this.setState({downloader: downloader});
|
||||||
var instance = this;
|
downloader.download(url, filename, mimetype, (loaded) => {
|
||||||
downloader.download(url, filename, mimetype, function(e) {
|
this.setState({progress: loaded / this.props.size});
|
||||||
instance.setState({progress: e.loaded / instance.props.size});
|
}).then(() => {
|
||||||
}).then(function() {
|
this.setState({downloader: null, progress: 0});
|
||||||
instance.setState({downloader: null, progress: 0});
|
}).catch((err) => {
|
||||||
}).catch(function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
instance.props.onError("Error downloading file: " + err.message, "err");
|
this.props.onError("Error downloading file: " + err.message, "err");
|
||||||
}
|
}
|
||||||
instance.setState({downloader: null, progress: 0});
|
this.setState({downloader: null, progress: 0});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3538,14 +3518,14 @@ class Attachment extends React.Component {
|
|||||||
url = this.props.downloadUrl;
|
url = this.props.downloadUrl;
|
||||||
helperFunc = null;
|
helperFunc = null;
|
||||||
}
|
}
|
||||||
var progress = this.props.uploader ? this.props.progress : this.state.progress;
|
|
||||||
return (
|
return (
|
||||||
<div className="attachment">
|
<div className="attachment">
|
||||||
<div><i className="material-icons big gray">insert_drive_file</i></div>
|
<div><i className="material-icons big gray">insert_drive_file</i></div>
|
||||||
<div className="flex-column">
|
<div className="flex-column">
|
||||||
<div>{filename} {size}</div>
|
<div>{filename} {size}</div>
|
||||||
{this.props.uploader || this.state.downloader ?
|
{this.props.uploader || this.state.downloader ?
|
||||||
<FileUploadProgress progress={progress} onCancel={this.handleCancel} />
|
<FileProgress progress={this.props.uploader ? this.props.progress : this.state.progress}
|
||||||
|
onCancel={this.handleCancel} />
|
||||||
:
|
:
|
||||||
<div><a href={url} download={this.props.filename} onClick={helperFunc} >
|
<div><a href={url} download={this.props.filename} onClick={helperFunc} >
|
||||||
<i className="material-icons">file_download</i> save
|
<i className="material-icons">file_download</i> save
|
||||||
@@ -3563,7 +3543,6 @@ class MessagesView extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
messages: [],
|
messages: [],
|
||||||
uploadProgress: 0,
|
|
||||||
onlineSubs: [],
|
onlineSubs: [],
|
||||||
topic: '',
|
topic: '',
|
||||||
title: '',
|
title: '',
|
||||||
@@ -3665,8 +3644,7 @@ class MessagesView extends React.Component {
|
|||||||
onlineSubs: subs,
|
onlineSubs: subs,
|
||||||
topic: props.topic,
|
topic: props.topic,
|
||||||
imagePreview: null,
|
imagePreview: null,
|
||||||
scrollPosition: 0,
|
scrollPosition: 0
|
||||||
uploadProgress: 0,
|
|
||||||
});
|
});
|
||||||
tryToResubscribe = true;
|
tryToResubscribe = true;
|
||||||
|
|
||||||
@@ -3796,10 +3774,13 @@ class MessagesView extends React.Component {
|
|||||||
if (topic.isNewMessage(msg.seq)) {
|
if (topic.isNewMessage(msg.seq)) {
|
||||||
newState.scrollPosition = 0;
|
newState.scrollPosition = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aknowledge all messages, including own messges.
|
// Aknowledge all messages, including own messges.
|
||||||
this.props.readTimerHandler(function() {
|
if (topic.msgStatus(msg) != Tinode.MESSAGE_STATUS_PENDING) {
|
||||||
|
this.props.readTimerHandler(() => {
|
||||||
topic.noteRead(msg.seq);
|
topic.noteRead(msg.seq);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
this.props.onData(msg);
|
this.props.onData(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3888,7 +3869,7 @@ class MessagesView extends React.Component {
|
|||||||
previousFrom = msg.from;
|
previousFrom = msg.from;
|
||||||
|
|
||||||
var isReply = !(msg.from === this.props.myUserId);
|
var isReply = !(msg.from === this.props.myUserId);
|
||||||
var msgDeliveryStatus = topic.msgStatus(msg);
|
var deliveryStatus = topic.msgStatus(msg);
|
||||||
|
|
||||||
var userName, userAvatar, userFrom, chatBoxClass;
|
var userName, userAvatar, userFrom, chatBoxClass;
|
||||||
if (groupTopic) {
|
if (groupTopic) {
|
||||||
@@ -3907,7 +3888,7 @@ class MessagesView extends React.Component {
|
|||||||
<ChatMessage content={msg.content} mimeType={msg.head ? msg.head.mime : null}
|
<ChatMessage content={msg.content} mimeType={msg.head ? msg.head.mime : null}
|
||||||
timestamp={msg.ts} response={isReply} seq={msg.seq}
|
timestamp={msg.ts} response={isReply} seq={msg.seq}
|
||||||
userFrom={userFrom} userName={userName} userAvatar={userAvatar}
|
userFrom={userFrom} userName={userName} userAvatar={userAvatar}
|
||||||
sequence={sequence} received={msgDeliveryStatus}
|
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.handleImagePreview}
|
||||||
@@ -4601,12 +4582,15 @@ class AppView extends React.Component {
|
|||||||
|
|
||||||
// Sending "received" notifications
|
// Sending "received" notifications
|
||||||
tnData(data) {
|
tnData(data) {
|
||||||
|
let topic = Tinode.getTopic(data.topic);
|
||||||
|
if (topic.msgStatus(data) != Tinode.MESSAGE_STATUS_PENDING) {
|
||||||
clearTimeout(this.receivedTimer);
|
clearTimeout(this.receivedTimer);
|
||||||
this.receivedTimer = setTimeout(function() {
|
this.receivedTimer = setTimeout(() => {
|
||||||
this.receivedTimer = undefined;
|
this.receivedTimer = undefined;
|
||||||
Tinode.getTopic(data.topic).noteRecv(data.seq);
|
topic.noteRecv(data.seq);
|
||||||
}, RECEIVED_DELAY);
|
}, RECEIVED_DELAY);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Fnd topic: find contacts by tokens */
|
/* Fnd topic: find contacts by tokens */
|
||||||
tnInitFind() {
|
tnInitFind() {
|
||||||
@@ -4697,6 +4681,10 @@ class AppView extends React.Component {
|
|||||||
handleSendMessage(msg, promise, uploader) {
|
handleSendMessage(msg, promise, uploader) {
|
||||||
let topic = Tinode.getTopic(this.state.topicSelected);
|
let topic = Tinode.getTopic(this.state.topicSelected);
|
||||||
msg = topic.createMessage(Drafty.parse(msg) || msg);
|
msg = topic.createMessage(Drafty.parse(msg) || msg);
|
||||||
|
msg._uploader = uploader;
|
||||||
|
promise.catch((err) => {
|
||||||
|
this.handleError(err.message, "err");
|
||||||
|
});
|
||||||
|
|
||||||
topic.publishDraft(msg, promise)
|
topic.publishDraft(msg, promise)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user