185 lines
6.4 KiB
React
185 lines
6.4 KiB
React
// Send message form.
|
|
|
|
import React from 'react';
|
|
|
|
export default class SendMessage extends React.PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
message: '',
|
|
// Make initial keypress time as if it happened 5001 milliseconds in the past.
|
|
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
|
|
};
|
|
|
|
this.handlePasteEvent = this.handlePasteEvent.bind(this);
|
|
this.handleAttachImage = this.handleAttachImage.bind(this);
|
|
this.handleAttachFile = this.handleAttachFile.bind(this);
|
|
this.handleSend = this.handleSend.bind(this);
|
|
this.handleKeyPress = this.handleKeyPress.bind(this);
|
|
this.handleMessageTyping = this.handleMessageTyping.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.pasteFile.addEventListener('paste', this.handlePasteEvent, false);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.pasteFile.removeEventListener('paste', this.handlePasteEvent, false)
|
|
}
|
|
|
|
handlePasteEvent(e) {
|
|
if (this.props.disabled) {
|
|
return;
|
|
}
|
|
// FIXME: handle large files too.
|
|
if (filePasted(e,
|
|
(bits, mime, width, height, fname) => {
|
|
this.props.sendMessage(Drafty.insertImage(null,
|
|
0, mime, bits, width, height, fname));
|
|
},
|
|
(mime, bits, fname) => {
|
|
this.props.sendMessage(Drafty.attachFile(null, mime, bits, fname));
|
|
},
|
|
this.props.onError)) {
|
|
|
|
// If a file was pasted, don't paste base64 data into input field.
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
handleAttachImage(e) {
|
|
if (e.target.files && e.target.files.length > 0) {
|
|
let file = e.target.files[0];
|
|
// Check if the uploaded file is indeed an image and if it isn't too large.
|
|
if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {
|
|
// Convert image for size or format.
|
|
imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false,
|
|
// Success
|
|
(bits, mime, width, height, fname) => {
|
|
this.props.sendMessage(Drafty.insertImage(null,
|
|
0, mime, bits, width, height, fname));
|
|
},
|
|
// Failure
|
|
(err) => {
|
|
this.props.onError(err, 'err');
|
|
});
|
|
} else {
|
|
// Image can be uploaded as is. No conversion is needed.
|
|
imageFileToBase64(file,
|
|
// Success
|
|
(bits, mime, width, height, fname) => {
|
|
this.props.sendMessage(Drafty.insertImage(null,
|
|
0, mime, bits, width, height, fname));
|
|
},
|
|
// Failure
|
|
(err) => {
|
|
this.props.onError(err, 'err');
|
|
}
|
|
);
|
|
}
|
|
}
|
|
// Clear the value so the same file can be uploaded again.
|
|
e.target.value = '';
|
|
}
|
|
|
|
handleAttachFile(e) {
|
|
if (e.target.files && e.target.files.length > 0) {
|
|
var file = e.target.files[0];
|
|
if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {
|
|
// Too large.
|
|
this.props.onError("The file size " + bytesToHumanSize(file.size) +
|
|
" exceeds the " + bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE) + " limit.", 'err');
|
|
} else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) {
|
|
// Too large to send inband - uploading out of band and sending as a link.
|
|
let uploader = this.props.tinode.getLargeFileHelper();
|
|
if (!uploader) {
|
|
this.props.onError("Cannot initiate file upload.");
|
|
return;
|
|
}
|
|
// Format data and initiate upload.
|
|
let uploadCompletionPromise = uploader.upload(file);
|
|
let msg = Drafty.attachFile(null, file.type, null, file.name, file.size, uploadCompletionPromise);
|
|
// Pass data and the uploader to the TinodeWeb.
|
|
this.props.sendMessage(msg, uploadCompletionPromise, uploader);
|
|
} else {
|
|
// Small enough to send inband.
|
|
fileToBase64(file,
|
|
(mime, bits, fname) => {
|
|
this.props.sendMessage(Drafty.attachFile(null, mime, bits, fname));
|
|
},
|
|
this.props.onError
|
|
);
|
|
}
|
|
}
|
|
// Clear the value so the same file can be uploaded again.
|
|
e.target.value = '';
|
|
}
|
|
|
|
handleSend() {
|
|
var message = this.state.message.trim();
|
|
if (message) {
|
|
this.props.sendMessage(this.state.message.trim());
|
|
this.setState({message: ''});
|
|
}
|
|
}
|
|
|
|
/* Send on Enter key */
|
|
handleKeyPress(e) {
|
|
// Remove this if you don't want Enter to trigger send
|
|
if (e.key === 'Enter') {
|
|
// Have Shift-Enter insert a line break instead
|
|
if (!e.shiftKey) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
this.handleSend();
|
|
}
|
|
}
|
|
}
|
|
|
|
handleMessageTyping(e) {
|
|
var newState = {message: e.target.value};
|
|
var now = new Date().getTime();
|
|
if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) {
|
|
var topic = this.props.tinode.getTopic(this.props.topic);
|
|
if (topic.isSubscribed()) {
|
|
topic.noteKeyPress();
|
|
}
|
|
newState.keypressTimestamp = now;
|
|
}
|
|
this.setState(newState);
|
|
}
|
|
|
|
render() {
|
|
var prompt = this.props.disabled ? "Messaging disabled" : "New message";
|
|
var instance = this;
|
|
return (
|
|
<div id="send-message-panel">
|
|
{this.props.disabled ?
|
|
<i className="material-icons disabled">photo</i> :
|
|
<a href="javascript:;" onClick={function(e) {instance.attachImage.click();}} title="Add image">
|
|
<i className="material-icons secondary">photo</i>
|
|
</a>}
|
|
{this.props.disabled ?
|
|
<i className="material-icons disabled">attach_file</i> :
|
|
<a href="javascript:;" onClick={function(e) {instance.attachFile.click();}} title="Attach file">
|
|
<i className="material-icons secondary">attach_file</i>
|
|
</a>}
|
|
<textarea id="sendMessage" placeholder={prompt}
|
|
disabled={this.props.disabled} value={this.state.message}
|
|
onChange={this.handleMessageTyping} onKeyPress={this.handleKeyPress}
|
|
ref={function(ref) {instance.pasteFile = ref;}}
|
|
autoFocus />
|
|
{this.props.disabled ?
|
|
<i className="material-icons disabled">send</i> :
|
|
<a href="javascript:;" onClick={this.handleSend} title="Send"><i className="material-icons">send</i></a>}
|
|
<input type="file" ref={function(ref) {instance.attachFile = ref;}}
|
|
onChange={this.handleAttachFile} style={{display: 'none'}} />
|
|
<input type="file" ref={function(ref) {instance.attachImage = ref;}} accept="image/*"
|
|
onChange={this.handleAttachImage} style={{display: 'none'}} />
|
|
</div>
|
|
);
|
|
}
|
|
};
|