refactoring formatters for lazy display of scaled image previews (WIP)

This commit is contained in:
or-else
2021-09-17 22:10:41 -07:00
parent ae528ef8d6
commit c2c3b743b3
9 changed files with 249 additions and 38 deletions
+78 -9
View File
@@ -3,10 +3,11 @@ import { defineMessages } from 'react-intl';
import { Drafty } from 'tinode-sdk';
import LazyImage from '../widgets/lazy-image.jsx'
import UploadingImage from '../widgets/uploading-image.jsx'
import { IMAGE_THUMBNAIL_DIM, BROKEN_IMAGE_SIZE, REM_SIZE } from '../config.js';
import { fitImageSize } from './blob-helpers.js';
import { blobToBase64, fitImageSize, imageScaled } from './blob-helpers.js';
import { idToColorClass } from './strformat.js';
import { sanitizeImageUrl } from './utils.js';
@@ -34,9 +35,6 @@ function handleImageData(el, data, attr) {
attr.style = {
width: IMAGE_THUMBNAIL_DIM + 'px',
height: IMAGE_THUMBNAIL_DIM + 'px',
// Looks like a Chrome bug: broken image does not respect 'width' and 'height'.
minWidth: IMAGE_THUMBNAIL_DIM + 'px',
minHeight: IMAGE_THUMBNAIL_DIM + 'px'
};
return el;
}
@@ -124,8 +122,9 @@ export function fullFormatter(style, data, values, key) {
break;
case 'MN':
// Mention
if (data && data.hasOwnProperty('colorId')) {
attr.className = 'mn-dark-color' + data.colorId;
attr.className = 'mention'
if (data) {
attr.className += ' ' + idToColorClass(data.val, false, true);
}
break;
case 'FM':
@@ -230,6 +229,21 @@ export function previewFormatter(style, data, values, key) {
};
// Converts Drafty object into a quoted reply. 'this' is set by the caller.
function inlineImageAttr(attr, data) {
attr.style = {
width: IMAGE_THUMBNAIL_DIM + 'px',
height: IMAGE_THUMBNAIL_DIM + 'px',
}
attr.className = 'inline-image';
attr.alt = shortenFileName((data && data.name) || this.formatMessage(messages.drafty_image));
if (!data) {
attr.src = 'img/broken_image.png';
}
attr.title = attr.alt;
return attr;
}
// Displays a portion of Drafty within 'QQ' quotes. 'this' is set by the caller.
// 'this' must contain:
// formatMessage: this.props.intl.formatMessage
// messages: formatjs messages defined with defineMessages.
@@ -242,9 +256,8 @@ export function quoteFormatter(style, data, values, key) {
attr.key = key;
switch(style) {
case 'IM':
const img = handleImageData.call(this, el, data, attr);
values = [React.createElement(img, attr, null), ' ',
shortenFileName((data && data.name) || this.formatMessage(messages.drafty_image))];
attr = inlineImageAttr.call(this, attr, data);
values = [React.createElement('img', attr, null), ' ', attr.alt];
el = React.Fragment;
// Fragment attributes.
attr = {key: key};
@@ -281,3 +294,59 @@ export function quoteFormatter(style, data, values, key) {
}
return previewFormatter.call(this, style, data, values, key);
}
// Create image thumbnail suitable for inclusion in a quote.
function quoteImage(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) : Promise.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 => {
// Cut the square from the center of the image and shrink it.
return imageScaled(blob, IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, true)
}).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;
data.src = URL.createObjectURL(scaled.blob);
return blobToBase64(scaled.blob);
}).then(b64 => {
data.val = b64.bits;
return data;
}).catch(err => {
delete data.val;
delete data.src;
data.width = IMAGE_THUMBNAIL_DIM;
data.height = IMAGE_THUMBNAIL_DIM;
// Rethrow.
throw err;
});
}
// Create a preview of a reply.
export function replyFormatter(style, data, values, key) {
if (style != 'IM') {
return quoteFormatter.call(this, style, data, values, key);
}
const attr = inlineImageAttr.call(this, {key: key}, data);
attr.whenDone = quoteImage.call(this, data);
values = [React.createElement(LazyImage, attr, null), ' ', attr.alt];
return React.createElement(React.Fragment, {key: key}, values);
}