// Send message form. import React from 'react'; import { defineMessages, injectIntl } from 'react-intl'; import { Drafty } from 'tinode-sdk'; import { KEYPRESS_DELAY, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js'; import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64, imageFileScaledToBase64 } from '../lib/blob-helpers.js'; import { bytesToHumanSize } from '../lib/strformat.js'; const messages = defineMessages({ 'messaging_disabled': { id: 'messaging_disabled_prompt', defaultMessage: 'Messaging disabled', description: 'Prompt in SendMessage in read-only topic' }, 'type_new_message': { id: 'new_message_prompt', defaultMessage: 'New message', description: 'Prompt in SendMessage in read-only topic' }, 'file_attachment_too_large': { id: 'file_attachment_too_large', defaultMessage: 'The file size {size} exceeds the {limit} limit.', description: 'Error message when attachment is too large' }, 'cannot_initiate_upload': { id: 'cannot_initiate_file_upload', defaultMessage: 'Cannot initiate file upload.', description: 'Generic error messagewhen attachment fails' }, }); 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) { const {formatMessage} = this.props.intl; 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(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. let uploader = this.props.tinode.getLargeFileHelper(); if (!uploader) { this.props.onError(formatMessage(messages.cannot_initiate_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() { const {formatMessage} = this.props.intl; const prompt = this.props.disabled ? formatMessage(messages.messaging_disabled) : formatMessage(messages.type_new_message); let instance = this; return (