wip: handling previews/cancellations while uploading

This commit is contained in:
or-else
2020-10-22 17:40:40 -07:00
parent 53c1f9fd0b
commit cede800726
10 changed files with 201 additions and 95 deletions
+3
View File
@@ -69,6 +69,9 @@ export const MAX_EXTERN_ATTACHMENT_SIZE = 1 << 23;
// to adjust it to 1600 or 2400 for production.
export const MAX_IMAGE_DIM = 768;
// Linear dimensions of image preview: shrink image under this size for previews.
export const IMAGE_PREVIEW_DIM = 64;
// Maximum number of online users to be shown in a topic title bar. Others will be
// hidden under "+X more"
export const MAX_ONLINE_IN_TOPIC = 4;
+13 -8
View File
@@ -56,10 +56,14 @@ export function fitImageSize(width, height, maxWidth, maxHeight, forceSquare) {
// Ensure file's extension matches mime content type
export function fileNameForMime(fname, mime) {
var idx = SUPPORTED_IMAGE_FORMATS.indexOf(mime);
var ext = MIME_EXTENSIONS[idx];
const idx = SUPPORTED_IMAGE_FORMATS.indexOf(mime);
if (idx < 0 || !fname) {
// Unknown mime or empty name.
return fname;
}
const ext = MIME_EXTENSIONS[idx];
var at = fname.lastIndexOf('.');
const at = fname.lastIndexOf('.');
if (at >= 0) {
fname = fname.substring(0, at);
}
@@ -68,7 +72,7 @@ export function fileNameForMime(fname, mime) {
// Scale uploaded image to fit under certain dimensions and byte size, optionally constraining to a square.
// On success calls onSuccess callback with the scaled image as Blob.
export function imageScaled(file, maxWidth, maxHeight, maxSize, forceSquare, onSuccess, onError) {
export function imageScaled(fileOrBlob, maxWidth, maxHeight, maxSize, forceSquare, onSuccess, onError) {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onerror = function(err) {
@@ -92,7 +96,7 @@ export function imageScaled(file, maxWidth, maxHeight, maxSize, forceSquare, onS
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,
0, 0, dim.dstWidth, dim.dstHeight);
const mime = SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0 ? 'image/jpeg' : file.type;
const mime = SUPPORTED_IMAGE_FORMATS.indexOf(fileOrBlob.type) < 0 ? 'image/jpeg' : fileOrBlob.type;
// Generate blob to check size of the image.
let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
if (!blob) {
@@ -101,7 +105,8 @@ export function imageScaled(file, maxWidth, maxHeight, maxSize, forceSquare, onS
}
// Ensure the image is not too large. Shrink the image keeping the aspect ratio.
while (blob.length > maxSize) {
// Do nothing if maxsize is <= 0.
while (maxSize > 0 && blob.length > maxSize) {
dim.dstWidth = (dim.dstWidth * 0.70710678118) | 0;
dim.dstHeight = (dim.dstHeight * 0.70710678118) | 0;
canvas.width = dim.dstWidth;
@@ -114,9 +119,9 @@ export function imageScaled(file, maxWidth, maxHeight, maxSize, forceSquare, onS
}
canvas = null;
onSuccess(blob, mime, dim.dstWidth, dim.dstHeight, fileNameForMime(file.name, mime));
onSuccess(blob, mime, dim.dstWidth, dim.dstHeight, fileNameForMime(fileOrBlob.name, mime));
};
img.src = URL.createObjectURL(file);
img.src = URL.createObjectURL(fileOrBlob);
}
// Convert File to base64 string.
+46 -12
View File
@@ -15,8 +15,8 @@ import LoadSpinner from '../widgets/load-spinner.jsx';
import LogoView from './logo-view.jsx';
import SendMessage from '../widgets/send-message.jsx';
import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE,
MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY } from '../config.js';
import { DEFAULT_P2P_ACCESS_MODE, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY, MESSAGES_PAGE,
MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY } from '../config.js';
import { SUPPORTED_IMAGE_FORMATS, blobToBase64, filePasted, fileToBase64,
imageScaled, makeImageUrl } from '../lib/blob-helpers.js';
import HashNavigation from '../lib/navigation.js';
@@ -639,14 +639,19 @@ class MessagesView extends React.Component {
return;
}
const uploadCompletionPromise = uploader.upload(file);
const msg = Drafty.attachFile(null, file.type, null, file.name, file.size, uploadCompletionPromise);
const msg = Drafty.attachFile(null, {
mime: file.type,
filename: file.name,
size: file.size,
urlPromise: 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.sendMessage(Drafty.attachFile(null, {mime: mime, data: bits, filename: fname}));
},
this.props.onError
);
@@ -685,18 +690,47 @@ class MessagesView extends React.Component {
return;
}
const uploadCompletionPromise = uploader.upload(blob);
let msg = Drafty.insertImage(null, 0, mime, null, width, height, fname, blob.size, uploadCompletionPromise);
if (caption) {
msg = Drafty.appendLineBreak(msg);
msg = Drafty.append(msg, Drafty.init(caption));
}
// Pass data and the uploader to the TinodeWeb.
this.props.sendMessage(msg, uploadCompletionPromise, uploader);
// Make small preview to show while uploading.
imageScaled(blob, IMAGE_PREVIEW_DIM, IMAGE_PREVIEW_DIM, -1, false,
(tinyBlob) => {
// COnvert tiny image into base64 for serialization and previewing.
blobToBase64(tinyBlob, (blobMime, tinyBits64) => {
// content, insert_at, mime, base64bits, width, height, fname, size, refurl
let msg = Drafty.insertImage(null, 0, {
mime: mime,
_tempPreview: tinyBits64, // This preview will not be serialized.
width: width,
height: height,
filename: fname,
size: blob.size,
urlPromise: uploadCompletionPromise
});
if (caption) {
msg = Drafty.appendLineBreak(msg);
msg = Drafty.append(msg, Drafty.init(caption));
}
// Pass data and the uploader to the TinodeWeb.
this.props.sendMessage(msg, uploadCompletionPromise, uploader);
}
)},
// Failure
(err) => {
this.props.onError(err, 'err');
}
);
return;
}
blobToBase64(blob, (blobMime, bits64) => {
let msg = Drafty.insertImage(null, 0, blobMime, bits64, width, height, fname, blob.size);
let msg = Drafty.insertImage(null, 0, {
mime: blobMime,
preview: bits64, // Serializable preview
width: width,
height: height,
filename: fname,
size: blob.size
});
if (caption) {
msg = Drafty.appendLineBreak(msg);
msg = Drafty.append(msg, Drafty.init(caption));
+3 -3
View File
@@ -92,7 +92,7 @@ export default class ChatMessage extends React.Component {
attachments.push(<Attachment
tinode={this.props.tinode}
downloadUrl={Drafty.getDownloadUrl(att)}
filename={att.name} uploader={Drafty.isUploading(att)}
filename={att.name} uploader={Drafty.isProcessing(att)}
mimetype={att.mime} size={Drafty.getEntitySize(att)}
progress={this.state.progress}
onCancelUpload={this.handleCancelUpload}
@@ -174,10 +174,10 @@ function draftyFormatter(style, data, values, key) {
case 'IM':
// Additional processing for images
if (data) {
const uploading = Drafty.isUploading(data);
const uploading = Drafty.isProcessing(data);
if (uploading) {
// Use custom element instead of <img>.
el = 'UploadingImage';
el = UploadingImage;
}
attr.className = 'inline-image';
let dim = fitImageSize(data.width, data.height,