refactored blob helpers to use promises instead of callbacks
This commit is contained in:
+111
-97
@@ -77,132 +77,146 @@ export function fileNameForMime(fname, mime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scale uploaded image to fit under certain dimensions and byte size, optionally constraining to a square.
|
// 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.
|
// Returns a promise which is resolven on success or rejected on failure.
|
||||||
export function imageScaled(fileOrBlob, maxWidth, maxHeight, maxSize, forceSquare, onSuccess, onError) {
|
export function imageScaled(fileOrBlob, maxWidth, maxHeight, maxSize, forceSquare) {
|
||||||
const img = new Image();
|
return new Promise((resolve, reject) => {
|
||||||
img.crossOrigin = 'Anonymous';
|
const img = new Image();
|
||||||
img.onerror = function(err) {
|
img.crossOrigin = 'Anonymous';
|
||||||
onError("Image format unrecognized");
|
img.onerror = function(err) {
|
||||||
}
|
reject(new Error("Image format unrecognized"));
|
||||||
img.onload = async function() {
|
|
||||||
// Once the image is loaded, the URL is no longer needed.
|
|
||||||
URL.revokeObjectURL(img.src);
|
|
||||||
|
|
||||||
// Calculate the desired image dimensions.
|
|
||||||
const dim = fitImageSize(this.width, this.height, maxWidth, maxHeight, forceSquare);
|
|
||||||
if (!dim) {
|
|
||||||
onError("Invalid image");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
let canvas = document.createElement('canvas');
|
img.onload = async function() {
|
||||||
canvas.width = dim.dstWidth;
|
// Once the image is loaded, the URL is no longer needed.
|
||||||
canvas.height = dim.dstHeight;
|
URL.revokeObjectURL(img.src);
|
||||||
let ctx = canvas.getContext('2d');
|
|
||||||
ctx.imageSmoothingEnabled = true;
|
|
||||||
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,
|
|
||||||
0, 0, dim.dstWidth, dim.dstHeight);
|
|
||||||
|
|
||||||
const mime = SUPPORTED_IMAGE_FORMATS.includes(fileOrBlob.type) ? fileOrBlob.type : 'image/jpeg';
|
// Calculate the desired image dimensions.
|
||||||
// Generate blob to check size of the image.
|
const dim = fitImageSize(this.width, this.height, maxWidth, maxHeight, forceSquare);
|
||||||
let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
if (!dim) {
|
||||||
if (!blob) {
|
reject(new Error("Invalid image"));
|
||||||
onError("Unsupported image format");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
let canvas = document.createElement('canvas');
|
||||||
|
|
||||||
// Ensure the image is not too large. Shrink the image keeping the aspect ratio.
|
|
||||||
// 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;
|
canvas.width = dim.dstWidth;
|
||||||
canvas.height = dim.dstHeight;
|
canvas.height = dim.dstHeight;
|
||||||
ctx = canvas.getContext('2d');
|
let ctx = canvas.getContext('2d');
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,
|
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,
|
||||||
0, 0, dim.dstWidth, dim.dstHeight);
|
0, 0, dim.dstWidth, dim.dstHeight);
|
||||||
blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas = null;
|
const mime = SUPPORTED_IMAGE_FORMATS.includes(fileOrBlob.type) ? fileOrBlob.type : 'image/jpeg';
|
||||||
onSuccess(mime, blob, dim.dstWidth, dim.dstHeight, fileNameForMime(fileOrBlob.name, mime));
|
// Generate blob to check size of the image.
|
||||||
};
|
let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
||||||
img.src = URL.createObjectURL(fileOrBlob);
|
if (!blob) {
|
||||||
|
reject(new Error("Unsupported image format"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the image is not too large. Shrink the image keeping the aspect ratio.
|
||||||
|
// 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;
|
||||||
|
canvas.height = dim.dstHeight;
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,
|
||||||
|
0, 0, dim.dstWidth, dim.dstHeight);
|
||||||
|
blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas = null;
|
||||||
|
resolve({mime: mime, blob: blob, width: dim.dstWidth, height: dim.dstHeight, name: fileNameForMime(fileOrBlob.name, mime)});
|
||||||
|
};
|
||||||
|
img.src = URL.createObjectURL(fileOrBlob);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale and crop image according to specified dimensions.
|
// Scale and crop image according to specified dimensions.
|
||||||
// The coordinates are in unscaled image pixels, i.e. cut the rectangle first then scale it.
|
// The coordinates are in unscaled image pixels, i.e. cut the rectangle first then scale it.
|
||||||
export function imageCrop(mime, objURL, left, top, width, height, scale, onSuccess, onError) {
|
// returns a promise.
|
||||||
const img = new Image();
|
export function imageCrop(mime, objURL, left, top, width, height, scale) {
|
||||||
img.crossOrigin = 'Anonymous';
|
return new Promise((resolve, reject) => {
|
||||||
img.onerror = function(err) {
|
const img = new Image();
|
||||||
onError("Image format unrecognized");
|
img.crossOrigin = 'Anonymous';
|
||||||
}
|
img.onerror = (err) => {
|
||||||
img.onload = async function() {
|
reject(new Error("Image format unrecognized"));
|
||||||
// Once the image is loaded, the URL is no longer needed.
|
};
|
||||||
URL.revokeObjectURL(img.src);
|
img.onload = () => {
|
||||||
|
// Once the image is loaded, the URL is no longer needed.
|
||||||
|
URL.revokeObjectURL(img.src);
|
||||||
|
|
||||||
let canvas = document.createElement('canvas');
|
let canvas = document.createElement('canvas');
|
||||||
canvas.width = width * scale;
|
canvas.width = width * scale;
|
||||||
canvas.height = height * scale;
|
canvas.height = height * scale;
|
||||||
let ctx = canvas.getContext('2d');
|
let ctx = canvas.getContext('2d');
|
||||||
ctx.imageSmoothingEnabled = true;
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.drawImage(this, left, top, width, height, 0, 0, canvas.width, canvas.height);
|
ctx.drawImage(this, left, top, width, height, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
mime = SUPPORTED_IMAGE_FORMATS.includes(mime) ? mime : 'image/jpeg';
|
mime = SUPPORTED_IMAGE_FORMATS.includes(mime) ? mime : 'image/jpeg';
|
||||||
// Generate blob to check size of the image.
|
// Generate blob to check size of the image.
|
||||||
let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
canvas.toBlob((blob) => {
|
||||||
if (!blob) {
|
// Allow GC.
|
||||||
onError("Unsupported image format");
|
canvas = null;
|
||||||
return;
|
if (blob) {
|
||||||
}
|
resolve({mime: mime, blob: blob, width: width, height: height});
|
||||||
// Allow GC.
|
} else {
|
||||||
canvas = null;
|
reject(new Error("Unsupported image format"));
|
||||||
onSuccess(mime, blob, width, height);
|
}
|
||||||
};
|
}, mime);
|
||||||
img.src = objURL;
|
};
|
||||||
|
img.src = objURL;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert File to base64 string.
|
// Convert file to base64 string.
|
||||||
export function fileToBase64(file, onSuccess) {
|
export function fileToBase64(file) {
|
||||||
const reader = new FileReader();
|
return new Promise((resolve, reject) => {
|
||||||
reader.addEventListener('load', function() {
|
const reader = new FileReader();
|
||||||
onSuccess(file.type, reader.result.split(',')[1], file.name);
|
reader.onerror = (evt) => {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve({mime: file.type, bits: reader.result.split(',')[1], name: file.name});
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
});
|
});
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert Blob to base64 string.
|
// Convert Blob to base64 string. Returns a promise resolved with the base64 string and mime.
|
||||||
export function blobToBase64(blob, onSuccess) {
|
export function blobToBase64(blob) {
|
||||||
const reader = new FileReader();
|
return new Promise((resolve, reject) => {
|
||||||
reader.addEventListener('load', function() {
|
const reader = new FileReader();
|
||||||
onSuccess(blob.type, reader.result.split(',')[1]);
|
reader.onerror = (evt) => {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve({mime: blob.type, bits: reader.result.split(',')[1]});
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
});
|
});
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// File pasted from the clipboard. It's either an inline image or a file attachment.
|
// File pasted from the clipboard. It's either an inline image or a file attachment.
|
||||||
// FIXME: handle large files out of band.
|
|
||||||
export function filePasted(event, onImageSuccess, onAttachmentSuccess, onError) {
|
export function filePasted(event, onImageSuccess, onAttachmentSuccess, onError) {
|
||||||
var items = (event.clipboardData || event.originalEvent.clipboardData || {}).items;
|
const items = (event.clipboardData || event.originalEvent.clipboardData || {}).items;
|
||||||
for (var i in items) {
|
if (!Array.isArray(items)) {
|
||||||
var item = items[i];
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i in items) {
|
||||||
|
const item = items[i];
|
||||||
if (item.kind === 'file') {
|
if (item.kind === 'file') {
|
||||||
var file = item.getAsFile();
|
const file = item.getAsFile();
|
||||||
if (!file) {
|
if (!file) {
|
||||||
console.error("Failed to get file object from pasted file item", item.kind, item.type);
|
console.error("Failed to get file object from pasted file item", item.kind, item.type);
|
||||||
|
onError("Failed to get file object from pasted file item");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (file.type && file.type.split('/')[0] == 'image') {
|
if (file.type && file.type.split('/')[0] == 'image') {
|
||||||
// Handle inline image
|
onImageSuccess(file);
|
||||||
if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {
|
|
||||||
imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false, onImageSuccess, onError);
|
|
||||||
} else {
|
|
||||||
imageFileToBase64(file, onImageSuccess, onError);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Handle file attachment
|
onAttachmentSuccess(file);
|
||||||
fileToBase64(file, onAttachmentSuccess, onError)
|
|
||||||
}
|
}
|
||||||
// Indicate that the pasted data contains a file.
|
// Indicate that the pasted data contains a file.
|
||||||
return true;
|
return true;
|
||||||
@@ -263,7 +277,7 @@ export function base64ToBlob(str, mime) {
|
|||||||
|
|
||||||
return new Blob([buf], { type: mime });
|
return new Blob([buf], { type: mime });
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log("Failed to convert base64 to blob: ", err);
|
console.error("Failed to convert base64 to blob: ", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+46
-1
@@ -28,6 +28,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Size the already scaled image.
|
||||||
function handleImageData(el, data, attr) {
|
function handleImageData(el, data, attr) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
attr.src = 'img/broken_image.png';
|
attr.src = 'img/broken_image.png';
|
||||||
@@ -74,6 +75,50 @@ function handleImageData(el, data, attr) {
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function quotedImage(data) {
|
||||||
|
let promise;
|
||||||
|
// Get the blob from the image data.
|
||||||
|
if (data.val) {
|
||||||
|
const blob = base64ToBlob(data.val, data.mime);
|
||||||
|
promise = blob ? Promise.resolve(blob) : Prmise.reject(new Error("Invalid image"));
|
||||||
|
} else {
|
||||||
|
promise = fetch(this.authorizeURL(sanitizeImageUrl(data.ref))).then(evt => {
|
||||||
|
if (evt.ok) {
|
||||||
|
return evt.blob();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale the blob.
|
||||||
|
return promise
|
||||||
|
.then(blob => {
|
||||||
|
return imageScaled(blob, IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, false)
|
||||||
|
}).then(scaled => {
|
||||||
|
data.mime = scaled.mime;
|
||||||
|
data.size = scaled.blob.size;
|
||||||
|
data.width = scaled.width;
|
||||||
|
data.height = scaled.height;
|
||||||
|
delete data.ref;
|
||||||
|
// Keeping the original file name, if provided: ex.data.name;
|
||||||
|
|
||||||
|
return blobToBase64(scaled.blob);
|
||||||
|
}).then(b64 => {
|
||||||
|
data.val = b64.bits;
|
||||||
|
return data;
|
||||||
|
}).catch(err => {
|
||||||
|
delete data.val;
|
||||||
|
delete data.name;
|
||||||
|
data.width = IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.height = IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.maxWidth = IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.maxHeight = IMAGE_THUMBNAIL_DIM;
|
||||||
|
// Rethrow.
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function shortenFileName(filename) {
|
function shortenFileName(filename) {
|
||||||
return filename.length > 16 ? filename.slice(0, 7) + '…' + filename.slice(-7) : filename;
|
return filename.length > 16 ? filename.slice(0, 7) + '…' + filename.slice(-7) : filename;
|
||||||
}
|
}
|
||||||
@@ -229,7 +274,7 @@ export function previewFormatter(style, data, values, key) {
|
|||||||
return React.createElement(el, attr, values);
|
return React.createElement(el, attr, values);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Converts Drafty object into a quoted reply. 'this' is set by the caller.
|
// Displays a portion of Drafty within 'QQ' quotes. 'this' is set by the caller.
|
||||||
// 'this' must contain:
|
// 'this' must contain:
|
||||||
// formatMessage: this.props.intl.formatMessage
|
// formatMessage: this.props.intl.formatMessage
|
||||||
// messages: formatjs messages defined with defineMessages.
|
// messages: formatjs messages defined with defineMessages.
|
||||||
|
|||||||
+80
-102
@@ -704,12 +704,9 @@ class MessagesView extends React.Component {
|
|||||||
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
||||||
} else {
|
} else {
|
||||||
// Small enough to send inband.
|
// Small enough to send inband.
|
||||||
fileToBase64(file,
|
fileToBase64(file)
|
||||||
(mime, bits, fname) => {
|
.then(b64 => this.sendMessage(Drafty.attachFile(null, {mime: b64.mime, data: b64.bits, filename: b64.name})))
|
||||||
this.sendMessage(Drafty.attachFile(null, {mime: mime, data: bits, filename: fname}));
|
.catch(err => this.props.onError(err));
|
||||||
},
|
|
||||||
this.props.onError
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -752,13 +749,13 @@ class MessagesView extends React.Component {
|
|||||||
const uploadCompletionPromise = uploader.upload(blob);
|
const uploadCompletionPromise = uploader.upload(blob);
|
||||||
|
|
||||||
// Make small preview to show while uploading.
|
// Make small preview to show while uploading.
|
||||||
imageScaled(blob, IMAGE_PREVIEW_DIM, IMAGE_PREVIEW_DIM, -1, false,
|
imageScaled(blob, IMAGE_PREVIEW_DIM, IMAGE_PREVIEW_DIM, -1, false)
|
||||||
(tinyMine, tinyBlob) => {
|
// Convert tiny image into base64 for serialization and previewing.
|
||||||
// Convert tiny image into base64 for serialization and previewing.
|
.then(scaled => blobToBase64(scaled.blob))
|
||||||
blobToBase64(tinyBlob, (blobMime, tinyBits64) => {
|
.then(b64 => {
|
||||||
let msg = Drafty.insertImage(null, 0, {
|
let msg = Drafty.insertImage(null, 0, {
|
||||||
mime: mime,
|
mime: mime,
|
||||||
_tempPreview: tinyBits64, // This preview will not be serialized.
|
_tempPreview: b64.bits, // This preview will not be serialized.
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
filename: fname,
|
filename: fname,
|
||||||
@@ -771,32 +768,29 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
// Pass data and the uploader to the TinodeWeb.
|
// Pass data and the uploader to the TinodeWeb.
|
||||||
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
||||||
}
|
}).catch((err) => {
|
||||||
)},
|
|
||||||
// Failure
|
|
||||||
(err) => {
|
|
||||||
this.props.onError(err, 'err');
|
this.props.onError(err, 'err');
|
||||||
}
|
});
|
||||||
);
|
return;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload the image if it's too big to be send inband.
|
// Upload the image if it's too big to be send inband.
|
||||||
blobToBase64(blob, (blobMime, bits64) => {
|
blobToBase64(blob)
|
||||||
let msg = Drafty.insertImage(null, 0, {
|
.then(b64 => {
|
||||||
mime: blobMime,
|
let msg = Drafty.insertImage(null, 0, {
|
||||||
preview: bits64, // Serializable preview
|
mime: b64.mime,
|
||||||
width: width,
|
preview: b64.bits, // Serializable preview
|
||||||
height: height,
|
width: width,
|
||||||
filename: fname,
|
height: height,
|
||||||
size: blob.size
|
filename: fname,
|
||||||
|
size: blob.size
|
||||||
|
});
|
||||||
|
if (caption) {
|
||||||
|
msg = Drafty.appendLineBreak(msg);
|
||||||
|
msg = Drafty.append(msg, Drafty.parse(caption));
|
||||||
|
}
|
||||||
|
this.sendMessage(msg);
|
||||||
});
|
});
|
||||||
if (caption) {
|
|
||||||
msg = Drafty.appendLineBreak(msg);
|
|
||||||
msg = Drafty.append(msg, Drafty.parse(caption));
|
|
||||||
}
|
|
||||||
this.sendMessage(msg);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleAttachImage method is called when [Attach image] button is clicked.
|
// handleAttachImage method is called when [Attach image] button is clicked.
|
||||||
@@ -804,24 +798,20 @@ class MessagesView extends React.Component {
|
|||||||
const maxExternAttachmentSize = this.props.tinode.getServerLimit('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);
|
const maxExternAttachmentSize = this.props.tinode.getServerLimit('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);
|
||||||
|
|
||||||
// Get image dimensions and size, optionally scale it down.
|
// Get image dimensions and size, optionally scale it down.
|
||||||
imageScaled(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, maxExternAttachmentSize, false,
|
imageScaled(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, maxExternAttachmentSize, false)
|
||||||
// Success
|
.then(scaled => {
|
||||||
(mime, blob, width, height, fname) => {
|
|
||||||
this.setState({imagePreview: {
|
this.setState({imagePreview: {
|
||||||
url: URL.createObjectURL(blob),
|
url: URL.createObjectURL(scaled.blob),
|
||||||
blob: blob,
|
blob: scaled.blob,
|
||||||
filename: fname,
|
filename: scaled.fname,
|
||||||
width: width,
|
width: scaled.width,
|
||||||
height: height,
|
height: scaled.height,
|
||||||
size: blob.size,
|
size: scaled.blob.size,
|
||||||
type: mime
|
type: scaled.mime
|
||||||
}});
|
}});
|
||||||
},
|
}).catch(err => {
|
||||||
// Failure
|
|
||||||
(err) => {
|
|
||||||
this.props.onError(err, 'err');
|
this.props.onError(err, 'err');
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCancelUpload(seq, uploader) {
|
handleCancelUpload(seq, uploader) {
|
||||||
@@ -879,64 +869,52 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Turn all images into thumbnails.
|
const quotedImage = (data) => {
|
||||||
const promises = images.map((ex) => {
|
let promise;
|
||||||
return new Promise((resolve, reject) => {
|
// Get the blob from the image data.
|
||||||
const handleFailure = () => {
|
if (data.val) {
|
||||||
delete ex.data.val;
|
const blob = base64ToBlob(data.val, data.mime);
|
||||||
delete ex.data.name;
|
promise = blob ? Promise.resolve(blob) : Prmise.reject(new Error("Invalid image"));
|
||||||
ex.data.width = IMAGE_THUMBNAIL_DIM;
|
} else {
|
||||||
ex.data.height = IMAGE_THUMBNAIL_DIM;
|
promise = fetch(this.props.tinode.authorizeURL(sanitizeImageUrl(data.ref))).then(evt => {
|
||||||
ex.data.maxWidth = IMAGE_THUMBNAIL_DIM;
|
if (evt.ok) {
|
||||||
ex.data.maxHeight = IMAGE_THUMBNAIL_DIM;
|
return evt.blob();
|
||||||
};
|
|
||||||
const scale = (origBlob) => {
|
|
||||||
imageScaled(origBlob, IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, false,
|
|
||||||
// Success
|
|
||||||
(mime, blob, width, height, unused) => {
|
|
||||||
ex.data.mime = mime;
|
|
||||||
ex.data.size = blob.size;
|
|
||||||
ex.data.width = width;
|
|
||||||
ex.data.height = height;
|
|
||||||
delete ex.data.ref;
|
|
||||||
// Keeping the original file name, if provided: ex.data.name;
|
|
||||||
|
|
||||||
blobToBase64(blob, (blobMime, tinyBits64) => {
|
|
||||||
ex.data.val = tinyBits64;
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Failure
|
|
||||||
(err) => {
|
|
||||||
handleFailure();
|
|
||||||
reject(`Could not scale image: ${err}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ex.data.val) {
|
|
||||||
const b = base64ToBlob(ex.data.val, ex.data.mime);
|
|
||||||
if (b) {
|
|
||||||
scale(b);
|
|
||||||
} else {
|
} else {
|
||||||
handleFailure();
|
throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
fetch(this.props.tinode.authorizeURL(sanitizeImageUrl(ex.data.ref)))
|
}
|
||||||
.then(e => {
|
|
||||||
if (e.ok) {
|
|
||||||
return e.blob();
|
|
||||||
} else {
|
|
||||||
handleFailure();
|
|
||||||
reject(`Image fetch unsuccessful: ${e.status} ${e.statusText}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((b) => scale(b))
|
|
||||||
.catch((err) => reject(`Error fetching image data: ${err}`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.all(promises)
|
// Scale the blob.
|
||||||
|
return promise
|
||||||
|
.then(blob => {
|
||||||
|
return imageScaled(blob, IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, false)
|
||||||
|
}).then(scaled => {
|
||||||
|
data.mime = scaled.mime;
|
||||||
|
data.size = scaled.blob.size;
|
||||||
|
data.width = scaled.width;
|
||||||
|
data.height = scaled.height;
|
||||||
|
delete data.ref;
|
||||||
|
// Keeping the original file name, if provided: ex.data.name;
|
||||||
|
|
||||||
|
return blobToBase64(scaled.blob);
|
||||||
|
}).then(b64 => {
|
||||||
|
data.val = b64.bits;
|
||||||
|
return data;
|
||||||
|
}).catch(err => {
|
||||||
|
delete data.val;
|
||||||
|
delete data.name;
|
||||||
|
data.width = IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.height = IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.maxWidth = IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.maxHeight = IMAGE_THUMBNAIL_DIM;
|
||||||
|
// Rethrow.
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn all images into thumbnails.
|
||||||
|
Promise.all(images.map(ex => quotedImage(ex.data)))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.props.onError(err, 'err');
|
this.props.onError(err, 'err');
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,16 +34,14 @@ export default class AvatarCrop extends React.PureComponent {
|
|||||||
|
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
imageCrop(this.props.mime, this.props.avatar,
|
imageCrop(this.props.mime, this.props.avatar,
|
||||||
this.state.left, this.state.top,
|
this.state.left, this.state.top, this.state.width, this.state.height,
|
||||||
this.state.width, this.state.height,
|
this.state.scale)
|
||||||
this.state.scale,
|
.then(img => {
|
||||||
(mime, blob, width, height) => {
|
this.props.onSubmit(img.mime, img.blob, img.width, img.height);
|
||||||
this.props.onSubmit(mime, blob, width, height);
|
})
|
||||||
},
|
.catch(err => {
|
||||||
(err) => {
|
|
||||||
this.props.onError(err);
|
this.props.onError(err);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ export default class TopicDescEdit extends React.Component {
|
|||||||
// Utility method for converting cropped avatar blob to bytes for sending inband or
|
// Utility method for converting cropped avatar blob to bytes for sending inband or
|
||||||
// for uploading it to the server out of band.
|
// for uploading it to the server out of band.
|
||||||
uploadAvatar(mime, blob, width, height) {
|
uploadAvatar(mime, blob, width, height) {
|
||||||
const readyToUpload = (mime, blob, width, height) => {
|
const readyToUpload = (image) => {
|
||||||
|
let {mime, blob, width, height} = image;
|
||||||
if (blob.size > MAX_AVATAR_BYTES) {
|
if (blob.size > MAX_AVATAR_BYTES) {
|
||||||
// Too large to send inband - uploading out of band and sending as a link.
|
// Too large to send inband - uploading out of band and sending as a link.
|
||||||
const uploader = this.props.tinode.getLargeFileHelper();
|
const uploader = this.props.tinode.getLargeFileHelper();
|
||||||
@@ -127,24 +128,25 @@ export default class TopicDescEdit extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
this.setState({uploading: true});
|
this.setState({uploading: true});
|
||||||
// Convert blob to base64-encoded bits.
|
// Convert blob to base64-encoded bits.
|
||||||
blobToBase64(blob, (unused, base64bits) => {
|
blobToBase64(blob)
|
||||||
const du = makeImageUrl({data: base64bits, type: mime});
|
.then(b64 => {
|
||||||
this.setState({source: du});
|
const du = makeImageUrl({data: b64.bits, type: mime});
|
||||||
this.props.onUpdateTopicDesc(this.props.topic, theCard(null, du));
|
this.setState({source: du});
|
||||||
this.setState({uploading: false});
|
this.props.onUpdateTopicDesc(this.props.topic, theCard(null, du));
|
||||||
});
|
this.setState({uploading: false});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (width > AVATAR_SIZE || height > AVATAR_SIZE || width != height) {
|
if (width > AVATAR_SIZE || height > AVATAR_SIZE || width != height) {
|
||||||
// Avatar is not square or too large even after cropping. Shrink it and make square.
|
// Avatar is not square or too large even after cropping. Shrink it and make square.
|
||||||
imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true,
|
imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true)
|
||||||
readyToUpload,
|
.then(scaled => readyToUpload)
|
||||||
(err) => {
|
.catch(err => {
|
||||||
this.props.onError(err, 'err');
|
this.props.onError(err, 'err');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
readyToUpload(mime, blob, width, height);
|
readyToUpload({mime: mime, blob: blob, width: width, height: height});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+256
-179
@@ -176,120 +176,159 @@ function fileNameForMime(fname, mime) {
|
|||||||
|
|
||||||
return fname + '.' + ext;
|
return fname + '.' + ext;
|
||||||
}
|
}
|
||||||
function imageScaled(fileOrBlob, maxWidth, maxHeight, maxSize, forceSquare, onSuccess, onError) {
|
function imageScaled(fileOrBlob, maxWidth, maxHeight, maxSize, forceSquare) {
|
||||||
const img = new Image();
|
return new Promise((resolve, reject) => {
|
||||||
img.crossOrigin = 'Anonymous';
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'Anonymous';
|
||||||
|
|
||||||
img.onerror = function (err) {
|
img.onerror = function (err) {
|
||||||
onError("Image format unrecognized");
|
reject(new Error("Image format unrecognized"));
|
||||||
};
|
};
|
||||||
|
|
||||||
img.onload = async function () {
|
img.onload = async function () {
|
||||||
URL.revokeObjectURL(img.src);
|
URL.revokeObjectURL(img.src);
|
||||||
const dim = fitImageSize(this.width, this.height, maxWidth, maxHeight, forceSquare);
|
const dim = fitImageSize(this.width, this.height, maxWidth, maxHeight, forceSquare);
|
||||||
|
|
||||||
if (!dim) {
|
if (!dim) {
|
||||||
onError("Invalid image");
|
reject(new Error("Invalid image"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canvas = document.createElement('canvas');
|
let canvas = document.createElement('canvas');
|
||||||
canvas.width = dim.dstWidth;
|
|
||||||
canvas.height = dim.dstHeight;
|
|
||||||
let ctx = canvas.getContext('2d');
|
|
||||||
ctx.imageSmoothingEnabled = true;
|
|
||||||
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight, 0, 0, dim.dstWidth, dim.dstHeight);
|
|
||||||
const mime = SUPPORTED_IMAGE_FORMATS.includes(fileOrBlob.type) ? fileOrBlob.type : 'image/jpeg';
|
|
||||||
let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
|
||||||
|
|
||||||
if (!blob) {
|
|
||||||
onError("Unsupported image format");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (maxSize > 0 && blob.length > maxSize) {
|
|
||||||
dim.dstWidth = dim.dstWidth * 0.70710678118 | 0;
|
|
||||||
dim.dstHeight = dim.dstHeight * 0.70710678118 | 0;
|
|
||||||
canvas.width = dim.dstWidth;
|
canvas.width = dim.dstWidth;
|
||||||
canvas.height = dim.dstHeight;
|
canvas.height = dim.dstHeight;
|
||||||
ctx = canvas.getContext('2d');
|
let ctx = canvas.getContext('2d');
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight, 0, 0, dim.dstWidth, dim.dstHeight);
|
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight, 0, 0, dim.dstWidth, dim.dstHeight);
|
||||||
blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
const mime = SUPPORTED_IMAGE_FORMATS.includes(fileOrBlob.type) ? fileOrBlob.type : 'image/jpeg';
|
||||||
}
|
let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
||||||
|
|
||||||
canvas = null;
|
if (!blob) {
|
||||||
onSuccess(mime, blob, dim.dstWidth, dim.dstHeight, fileNameForMime(fileOrBlob.name, mime));
|
reject(new Error("Unsupported image format"));
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
img.src = URL.createObjectURL(fileOrBlob);
|
while (maxSize > 0 && blob.length > maxSize) {
|
||||||
}
|
dim.dstWidth = dim.dstWidth * 0.70710678118 | 0;
|
||||||
function imageCrop(mime, objURL, left, top, width, height, scale, onSuccess, onError) {
|
dim.dstHeight = dim.dstHeight * 0.70710678118 | 0;
|
||||||
const img = new Image();
|
canvas.width = dim.dstWidth;
|
||||||
img.crossOrigin = 'Anonymous';
|
canvas.height = dim.dstHeight;
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight, 0, 0, dim.dstWidth, dim.dstHeight);
|
||||||
|
blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
||||||
|
}
|
||||||
|
|
||||||
img.onerror = function (err) {
|
canvas = null;
|
||||||
onError("Image format unrecognized");
|
resolve({
|
||||||
};
|
mime: mime,
|
||||||
|
blob: blob,
|
||||||
|
width: dim.dstWidth,
|
||||||
|
height: dim.dstHeight,
|
||||||
|
name: fileNameForMime(fileOrBlob.name, mime)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
img.onload = async function () {
|
img.src = URL.createObjectURL(fileOrBlob);
|
||||||
URL.revokeObjectURL(img.src);
|
|
||||||
let canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width * scale;
|
|
||||||
canvas.height = height * scale;
|
|
||||||
let ctx = canvas.getContext('2d');
|
|
||||||
ctx.imageSmoothingEnabled = true;
|
|
||||||
ctx.drawImage(this, left, top, width, height, 0, 0, canvas.width, canvas.height);
|
|
||||||
mime = SUPPORTED_IMAGE_FORMATS.includes(mime) ? mime : 'image/jpeg';
|
|
||||||
let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));
|
|
||||||
|
|
||||||
if (!blob) {
|
|
||||||
onError("Unsupported image format");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas = null;
|
|
||||||
onSuccess(mime, blob, width, height);
|
|
||||||
};
|
|
||||||
|
|
||||||
img.src = objURL;
|
|
||||||
}
|
|
||||||
function fileToBase64(file, onSuccess) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.addEventListener('load', function () {
|
|
||||||
onSuccess(file.type, reader.result.split(',')[1], file.name);
|
|
||||||
});
|
});
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
}
|
||||||
function blobToBase64(blob, onSuccess) {
|
function imageCrop(mime, objURL, left, top, width, height, scale) {
|
||||||
const reader = new FileReader();
|
return new Promise((resolve, reject) => {
|
||||||
reader.addEventListener('load', function () {
|
const img = new Image();
|
||||||
onSuccess(blob.type, reader.result.split(',')[1]);
|
img.crossOrigin = 'Anonymous';
|
||||||
|
|
||||||
|
img.onerror = err => {
|
||||||
|
reject(new Error("Image format unrecognized"));
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
URL.revokeObjectURL(img.src);
|
||||||
|
let canvas = document.createElement('canvas');
|
||||||
|
canvas.width = width * scale;
|
||||||
|
canvas.height = height * scale;
|
||||||
|
let ctx = canvas.getContext('2d');
|
||||||
|
ctx.imageSmoothingEnabled = true;
|
||||||
|
ctx.drawImage(this, left, top, width, height, 0, 0, canvas.width, canvas.height);
|
||||||
|
mime = SUPPORTED_IMAGE_FORMATS.includes(mime) ? mime : 'image/jpeg';
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
canvas = null;
|
||||||
|
|
||||||
|
if (blob) {
|
||||||
|
resolve({
|
||||||
|
mime: mime,
|
||||||
|
blob: blob,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Error("Unsupported image format"));
|
||||||
|
}
|
||||||
|
}, mime);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = objURL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function fileToBase64(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onerror = evt => {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve({
|
||||||
|
mime: file.type,
|
||||||
|
bits: reader.result.split(',')[1],
|
||||||
|
name: file.name
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function blobToBase64(blob) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onerror = evt => {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve({
|
||||||
|
mime: blob.type,
|
||||||
|
bits: reader.result.split(',')[1]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
});
|
});
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
}
|
}
|
||||||
function filePasted(event, onImageSuccess, onAttachmentSuccess, onError) {
|
function filePasted(event, onImageSuccess, onAttachmentSuccess, onError) {
|
||||||
var items = (event.clipboardData || event.originalEvent.clipboardData || {}).items;
|
const items = (event.clipboardData || event.originalEvent.clipboardData || {}).items;
|
||||||
|
|
||||||
for (var i in items) {
|
if (!Array.isArray(items)) {
|
||||||
var item = items[i];
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i in items) {
|
||||||
|
const item = items[i];
|
||||||
|
|
||||||
if (item.kind === 'file') {
|
if (item.kind === 'file') {
|
||||||
var file = item.getAsFile();
|
const file = item.getAsFile();
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
console.error("Failed to get file object from pasted file item", item.kind, item.type);
|
console.error("Failed to get file object from pasted file item", item.kind, item.type);
|
||||||
|
onError("Failed to get file object from pasted file item");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.type && file.type.split('/')[0] == 'image') {
|
if (file.type && file.type.split('/')[0] == 'image') {
|
||||||
if (file.size > _config_js__WEBPACK_IMPORTED_MODULE_0__.MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) {
|
onImageSuccess(file);
|
||||||
imageFileScaledToBase64(file, _config_js__WEBPACK_IMPORTED_MODULE_0__.MAX_IMAGE_DIM, _config_js__WEBPACK_IMPORTED_MODULE_0__.MAX_IMAGE_DIM, false, onImageSuccess, onError);
|
|
||||||
} else {
|
|
||||||
imageFileToBase64(file, onImageSuccess, onError);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fileToBase64(file, onAttachmentSuccess, onError);
|
onAttachmentSuccess(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -341,7 +380,7 @@ function base64ToBlob(str, mime) {
|
|||||||
type: mime
|
type: mime
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Failed to convert base64 to blob: ", err);
|
console.error("Failed to convert base64 to blob: ", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -449,6 +488,45 @@ function handleImageData(el, data, attr) {
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function quotedImage(data) {
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
if (data.val) {
|
||||||
|
const blob = base64ToBlob(data.val, data.mime);
|
||||||
|
promise = blob ? Promise.resolve(blob) : Prmise.reject(new Error("Invalid image"));
|
||||||
|
} else {
|
||||||
|
promise = fetch(this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_7__.sanitizeImageUrl)(data.ref))).then(evt => {
|
||||||
|
if (evt.ok) {
|
||||||
|
return evt.blob();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then(blob => {
|
||||||
|
return imageScaled(blob, _config_js__WEBPACK_IMPORTED_MODULE_4__.IMAGE_THUMBNAIL_DIM, _config_js__WEBPACK_IMPORTED_MODULE_4__.IMAGE_THUMBNAIL_DIM, -1, false);
|
||||||
|
}).then(scaled => {
|
||||||
|
data.mime = scaled.mime;
|
||||||
|
data.size = scaled.blob.size;
|
||||||
|
data.width = scaled.width;
|
||||||
|
data.height = scaled.height;
|
||||||
|
delete data.ref;
|
||||||
|
return blobToBase64(scaled.blob);
|
||||||
|
}).then(b64 => {
|
||||||
|
data.val = b64.bits;
|
||||||
|
return data;
|
||||||
|
}).catch(err => {
|
||||||
|
delete data.val;
|
||||||
|
delete data.name;
|
||||||
|
data.width = _config_js__WEBPACK_IMPORTED_MODULE_4__.IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.height = _config_js__WEBPACK_IMPORTED_MODULE_4__.IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.maxWidth = _config_js__WEBPACK_IMPORTED_MODULE_4__.IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.maxHeight = _config_js__WEBPACK_IMPORTED_MODULE_4__.IMAGE_THUMBNAIL_DIM;
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function shortenFileName(filename) {
|
function shortenFileName(filename) {
|
||||||
return filename.length > 16 ? filename.slice(0, 7) + '…' + filename.slice(-7) : filename;
|
return filename.length > 16 ? filename.slice(0, 7) + '…' + filename.slice(-7) : filename;
|
||||||
}
|
}
|
||||||
@@ -3836,13 +3914,11 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
});
|
});
|
||||||
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
||||||
} else {
|
} else {
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.fileToBase64)(file, (mime, bits, fname) => {
|
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.fileToBase64)(file).then(b64 => this.sendMessage(Drafty.attachFile(null, {
|
||||||
this.sendMessage(Drafty.attachFile(null, {
|
mime: b64.mime,
|
||||||
mime: mime,
|
data: b64.bits,
|
||||||
data: bits,
|
filename: b64.name
|
||||||
filename: fname
|
}))).catch(err => this.props.onError(err));
|
||||||
}));
|
|
||||||
}, this.props.onError);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3882,35 +3958,33 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
}
|
}
|
||||||
|
|
||||||
const uploadCompletionPromise = uploader.upload(blob);
|
const uploadCompletionPromise = uploader.upload(blob);
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.imageScaled)(blob, _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_PREVIEW_DIM, _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_PREVIEW_DIM, -1, false, (tinyMine, tinyBlob) => {
|
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.imageScaled)(blob, _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_PREVIEW_DIM, _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_PREVIEW_DIM, -1, false).then(scaled => (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.blobToBase64)(scaled.blob)).then(b64 => {
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.blobToBase64)(tinyBlob, (blobMime, tinyBits64) => {
|
let msg = Drafty.insertImage(null, 0, {
|
||||||
let msg = Drafty.insertImage(null, 0, {
|
mime: mime,
|
||||||
mime: mime,
|
_tempPreview: b64.bits,
|
||||||
_tempPreview: tinyBits64,
|
width: width,
|
||||||
width: width,
|
height: height,
|
||||||
height: height,
|
filename: fname,
|
||||||
filename: fname,
|
size: blob.size,
|
||||||
size: blob.size,
|
urlPromise: uploadCompletionPromise
|
||||||
urlPromise: uploadCompletionPromise
|
|
||||||
});
|
|
||||||
|
|
||||||
if (caption) {
|
|
||||||
msg = Drafty.appendLineBreak(msg);
|
|
||||||
msg = Drafty.append(msg, Drafty.parse(caption));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
|
||||||
});
|
});
|
||||||
}, err => {
|
|
||||||
|
if (caption) {
|
||||||
|
msg = Drafty.appendLineBreak(msg);
|
||||||
|
msg = Drafty.append(msg, Drafty.parse(caption));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
||||||
|
}).catch(err => {
|
||||||
this.props.onError(err, 'err');
|
this.props.onError(err, 'err');
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.blobToBase64)(blob, (blobMime, bits64) => {
|
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.blobToBase64)(blob).then(b64 => {
|
||||||
let msg = Drafty.insertImage(null, 0, {
|
let msg = Drafty.insertImage(null, 0, {
|
||||||
mime: blobMime,
|
mime: b64.mime,
|
||||||
preview: bits64,
|
preview: b64.bits,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
filename: fname,
|
filename: fname,
|
||||||
@@ -3928,19 +4002,19 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
|
|
||||||
handleAttachImage(file) {
|
handleAttachImage(file) {
|
||||||
const maxExternAttachmentSize = this.props.tinode.getServerLimit('maxFileUploadSize', _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_EXTERN_ATTACHMENT_SIZE);
|
const maxExternAttachmentSize = this.props.tinode.getServerLimit('maxFileUploadSize', _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_EXTERN_ATTACHMENT_SIZE);
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.imageScaled)(file, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_IMAGE_DIM, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_IMAGE_DIM, maxExternAttachmentSize, false, (mime, blob, width, height, fname) => {
|
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.imageScaled)(file, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_IMAGE_DIM, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_IMAGE_DIM, maxExternAttachmentSize, false).then(scaled => {
|
||||||
this.setState({
|
this.setState({
|
||||||
imagePreview: {
|
imagePreview: {
|
||||||
url: URL.createObjectURL(blob),
|
url: URL.createObjectURL(scaled.blob),
|
||||||
blob: blob,
|
blob: scaled.blob,
|
||||||
filename: fname,
|
filename: scaled.fname,
|
||||||
width: width,
|
width: scaled.width,
|
||||||
height: height,
|
height: scaled.height,
|
||||||
size: blob.size,
|
size: scaled.blob.size,
|
||||||
type: mime
|
type: scaled.mime
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, err => {
|
}).catch(err => {
|
||||||
this.props.onError(err, 'err');
|
this.props.onError(err, 'err');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4003,56 +4077,47 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const promises = images.map(ex => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const handleFailure = () => {
|
|
||||||
delete ex.data.val;
|
|
||||||
delete ex.data.name;
|
|
||||||
ex.data.width = _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM;
|
|
||||||
ex.data.height = _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM;
|
|
||||||
ex.data.maxWidth = _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM;
|
|
||||||
ex.data.maxHeight = _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM;
|
|
||||||
};
|
|
||||||
|
|
||||||
const scale = origBlob => {
|
const quotedImage = data => {
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.imageScaled)(origBlob, _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM, _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM, -1, false, (mime, blob, width, height, unused) => {
|
let promise;
|
||||||
ex.data.mime = mime;
|
|
||||||
ex.data.size = blob.size;
|
|
||||||
ex.data.width = width;
|
|
||||||
ex.data.height = height;
|
|
||||||
delete ex.data.ref;
|
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.blobToBase64)(blob, (blobMime, tinyBits64) => {
|
|
||||||
ex.data.val = tinyBits64;
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
}, err => {
|
|
||||||
handleFailure();
|
|
||||||
reject(`Could not scale image: ${err}`);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ex.data.val) {
|
if (data.val) {
|
||||||
const b = (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.base64ToBlob)(ex.data.val, ex.data.mime);
|
const blob = (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.base64ToBlob)(data.val, data.mime);
|
||||||
|
promise = blob ? Promise.resolve(blob) : Prmise.reject(new Error("Invalid image"));
|
||||||
if (b) {
|
} else {
|
||||||
scale(b);
|
promise = fetch(this.props.tinode.authorizeURL((0,_lib_utils_js__WEBPACK_IMPORTED_MODULE_18__.sanitizeImageUrl)(data.ref))).then(evt => {
|
||||||
|
if (evt.ok) {
|
||||||
|
return evt.blob();
|
||||||
} else {
|
} else {
|
||||||
handleFailure();
|
throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
fetch(this.props.tinode.authorizeURL((0,_lib_utils_js__WEBPACK_IMPORTED_MODULE_18__.sanitizeImageUrl)(ex.data.ref))).then(e => {
|
}
|
||||||
if (e.ok) {
|
|
||||||
return e.blob();
|
return promise.then(blob => {
|
||||||
} else {
|
return (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.imageScaled)(blob, _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM, _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM, -1, false);
|
||||||
handleFailure();
|
}).then(scaled => {
|
||||||
reject(`Image fetch unsuccessful: ${e.status} ${e.statusText}`);
|
data.mime = scaled.mime;
|
||||||
}
|
data.size = scaled.blob.size;
|
||||||
}).then(b => scale(b)).catch(err => reject(`Error fetching image data: ${err}`));
|
data.width = scaled.width;
|
||||||
return;
|
data.height = scaled.height;
|
||||||
}
|
delete data.ref;
|
||||||
|
return (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.blobToBase64)(scaled.blob);
|
||||||
|
}).then(b64 => {
|
||||||
|
data.val = b64.bits;
|
||||||
|
return data;
|
||||||
|
}).catch(err => {
|
||||||
|
delete data.val;
|
||||||
|
delete data.name;
|
||||||
|
data.width = _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.height = _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.maxWidth = _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM;
|
||||||
|
data.maxHeight = _config_js__WEBPACK_IMPORTED_MODULE_14__.IMAGE_THUMBNAIL_DIM;
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
Promise.all(promises).catch(err => {
|
|
||||||
|
Promise.all(images.map(ex => quotedImage(ex.data))).catch(err => {
|
||||||
this.props.onError(err, 'err');
|
this.props.onError(err, 'err');
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -7321,9 +7386,9 @@ class AvatarCrop extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompo
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_3__.imageCrop)(this.props.mime, this.props.avatar, this.state.left, this.state.top, this.state.width, this.state.height, this.state.scale, (mime, blob, width, height) => {
|
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_3__.imageCrop)(this.props.mime, this.props.avatar, this.state.left, this.state.top, this.state.width, this.state.height, this.state.scale).then(img => {
|
||||||
this.props.onSubmit(mime, blob, width, height);
|
this.props.onSubmit(img.mime, img.blob, img.width, img.height);
|
||||||
}, err => {
|
}).catch(err => {
|
||||||
this.props.onError(err);
|
this.props.onError(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -12216,7 +12281,14 @@ class TopicDescEdit extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compon
|
|||||||
}
|
}
|
||||||
|
|
||||||
uploadAvatar(mime, blob, width, height) {
|
uploadAvatar(mime, blob, width, height) {
|
||||||
const readyToUpload = (mime, blob, width, height) => {
|
const readyToUpload = image => {
|
||||||
|
let {
|
||||||
|
mime,
|
||||||
|
blob,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
} = image;
|
||||||
|
|
||||||
if (blob.size > _config_js__WEBPACK_IMPORTED_MODULE_7__.MAX_AVATAR_BYTES) {
|
if (blob.size > _config_js__WEBPACK_IMPORTED_MODULE_7__.MAX_AVATAR_BYTES) {
|
||||||
const uploader = this.props.tinode.getLargeFileHelper();
|
const uploader = this.props.tinode.getLargeFileHelper();
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -12235,9 +12307,9 @@ class TopicDescEdit extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compon
|
|||||||
this.setState({
|
this.setState({
|
||||||
uploading: true
|
uploading: true
|
||||||
});
|
});
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_8__.blobToBase64)(blob, (unused, base64bits) => {
|
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_8__.blobToBase64)(blob).then(b64 => {
|
||||||
const du = (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_8__.makeImageUrl)({
|
const du = (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_8__.makeImageUrl)({
|
||||||
data: base64bits,
|
data: b64.bits,
|
||||||
type: mime
|
type: mime
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -12252,11 +12324,16 @@ class TopicDescEdit extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compon
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (width > _config_js__WEBPACK_IMPORTED_MODULE_7__.AVATAR_SIZE || height > _config_js__WEBPACK_IMPORTED_MODULE_7__.AVATAR_SIZE || width != height) {
|
if (width > _config_js__WEBPACK_IMPORTED_MODULE_7__.AVATAR_SIZE || height > _config_js__WEBPACK_IMPORTED_MODULE_7__.AVATAR_SIZE || width != height) {
|
||||||
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_8__.imageScaled)(blob, _config_js__WEBPACK_IMPORTED_MODULE_7__.AVATAR_SIZE, _config_js__WEBPACK_IMPORTED_MODULE_7__.AVATAR_SIZE, _config_js__WEBPACK_IMPORTED_MODULE_7__.MAX_EXTERN_ATTACHMENT_SIZE, true, readyToUpload, err => {
|
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_8__.imageScaled)(blob, _config_js__WEBPACK_IMPORTED_MODULE_7__.AVATAR_SIZE, _config_js__WEBPACK_IMPORTED_MODULE_7__.AVATAR_SIZE, _config_js__WEBPACK_IMPORTED_MODULE_7__.MAX_EXTERN_ATTACHMENT_SIZE, true).then(scaled => readyToUpload).catch(err => {
|
||||||
this.props.onError(err, 'err');
|
this.props.onError(err, 'err');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
readyToUpload(mime, blob, width, height);
|
readyToUpload({
|
||||||
|
mime: mime,
|
||||||
|
blob: blob,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user