Make thumbnails on quote load and on quote preview creation
This commit is contained in:
parent
37cac717cb
commit
13ce056830
4 changed files with 131 additions and 70 deletions
|
@ -610,6 +610,59 @@
|
||||||
return _.without(this.get('members'), me);
|
return _.without(this.get('members'), me);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
blobToArrayBuffer(blob) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
|
||||||
|
fileReader.onload = e => resolve(e.target.result);
|
||||||
|
fileReader.onerror = reject;
|
||||||
|
fileReader.onabort = reject;
|
||||||
|
|
||||||
|
fileReader.readAsArrayBuffer(blob);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async makeThumbnailAttachment(attachment) {
|
||||||
|
const attachmentWithData = await loadAttachmentData(attachment);
|
||||||
|
const { data, contentType } = attachmentWithData;
|
||||||
|
const objectUrl = this.makeObjectUrl(data, contentType);
|
||||||
|
const thumbnail = await Whisper.FileInputView.makeThumbnail(128, objectUrl);
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
|
||||||
|
const arrayBuffer = await this.blobToArrayBuffer(thumbnail);
|
||||||
|
const finalContentType = 'image/png';
|
||||||
|
const finalObjectUrl = this.makeObjectUrl(arrayBuffer, finalContentType);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: arrayBuffer,
|
||||||
|
objectUrl: finalObjectUrl,
|
||||||
|
contentType: finalContentType,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async makeQuote(quotedMessage) {
|
||||||
|
const contact = quotedMessage.getContact();
|
||||||
|
const attachments = quotedMessage.get('attachments');
|
||||||
|
|
||||||
|
return {
|
||||||
|
author: contact.id,
|
||||||
|
id: quotedMessage.get('sent_at'),
|
||||||
|
text: quotedMessage.get('body'),
|
||||||
|
attachments: await Promise.all((attachments || []).map(async (attachment) => {
|
||||||
|
const { contentType } = attachment;
|
||||||
|
const willMakeThumbnail = MIME.isImage(contentType);
|
||||||
|
|
||||||
|
return {
|
||||||
|
contentType,
|
||||||
|
fileName: attachment.fileName,
|
||||||
|
thumbnail: willMakeThumbnail
|
||||||
|
? await this.makeThumbnailAttachment(attachment)
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
sendMessage(body, attachments) {
|
sendMessage(body, attachments) {
|
||||||
this.queueJob(async () => {
|
this.queueJob(async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -1113,18 +1166,8 @@
|
||||||
|
|
||||||
const queryFirst = queryAttachments[0];
|
const queryFirst = queryAttachments[0];
|
||||||
try {
|
try {
|
||||||
queryMessage.attachments[0] = await loadAttachmentData(queryFirst);
|
|
||||||
|
|
||||||
// Note: it would be nice to take the full-size image and downsample it into
|
|
||||||
// a true thumbnail here.
|
|
||||||
queryMessage.updateImageUrl();
|
|
||||||
|
|
||||||
// We need to differentiate between messages we load from database and those
|
|
||||||
// already in memory. More cleanup needs to happen on messages from the database
|
|
||||||
// because they aren't tracked any other way.
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
message.quotedMessageFromDatabase = queryMessage;
|
message.quoteThumbnail = await this.makeThumbnailAttachment(queryFirst);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -1155,12 +1198,9 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const queryFirst = quotedAttachments[0];
|
const queryFirst = quotedAttachments[0];
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
quotedMessage.attributes.attachments[0] = await loadAttachmentData(queryFirst);
|
|
||||||
|
|
||||||
// Note: it would be nice to take the full-size image and downsample it into
|
// eslint-disable-next-line no-param-reassign
|
||||||
// a true thumbnail here.
|
message.quoteThumbnail = await this.makeThumbnailAttachment(queryFirst);
|
||||||
quotedMessage.updateImageUrl();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
console.log(
|
||||||
'Problem loading attachment data for quoted message',
|
'Problem loading attachment data for quoted message',
|
||||||
|
|
|
@ -188,6 +188,16 @@
|
||||||
if (this.quotedMessage) {
|
if (this.quotedMessage) {
|
||||||
this.quotedMessage = null;
|
this.quotedMessage = null;
|
||||||
}
|
}
|
||||||
|
const quote = this.get('quote');
|
||||||
|
const attachments = (quote && quote.attachments) || [];
|
||||||
|
attachments.forEach((attachment) => {
|
||||||
|
if (attachment.thumbnail && attachment.thumbnail.objectUrl) {
|
||||||
|
URL.revokeObjectURL(attachment.thumbnail.objectUrl);
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
attachment.thumbnail.objectUrl = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.revokeImageUrl();
|
this.revokeImageUrl();
|
||||||
},
|
},
|
||||||
revokeImageUrl() {
|
revokeImageUrl() {
|
||||||
|
@ -203,16 +213,6 @@
|
||||||
return this.imageUrl;
|
return this.imageUrl;
|
||||||
},
|
},
|
||||||
getQuoteObjectUrl() {
|
getQuoteObjectUrl() {
|
||||||
const fromDB = this.quotedMessageFromDatabase;
|
|
||||||
if (fromDB && fromDB.imageUrl) {
|
|
||||||
return fromDB.imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inMemory = this.quotedMessage;
|
|
||||||
if (inMemory && inMemory.imageUrl) {
|
|
||||||
return inMemory.imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const thumbnail = this.quoteThumbnail;
|
const thumbnail = this.quoteThumbnail;
|
||||||
if (thumbnail && thumbnail.objectUrl) {
|
if (thumbnail && thumbnail.objectUrl) {
|
||||||
return thumbnail.objectUrl;
|
return thumbnail.objectUrl;
|
||||||
|
@ -232,8 +232,11 @@
|
||||||
|
|
||||||
return ConversationController.get(author);
|
return ConversationController.get(author);
|
||||||
},
|
},
|
||||||
processAttachment(attachment, objectUrl) {
|
processAttachment(attachment, externalObjectUrl) {
|
||||||
const thumbnail = !objectUrl
|
const { thumbnail } = attachment;
|
||||||
|
const objectUrl = (thumbnail && thumbnail.objectUrl) || externalObjectUrl;
|
||||||
|
|
||||||
|
const thumbnailWithObjectUrl = !objectUrl
|
||||||
? null
|
? null
|
||||||
: Object.assign({}, attachment.thumbnail || {}, {
|
: Object.assign({}, attachment.thumbnail || {}, {
|
||||||
objectUrl,
|
objectUrl,
|
||||||
|
@ -242,7 +245,7 @@
|
||||||
return Object.assign({}, attachment, {
|
return Object.assign({}, attachment, {
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
isVoiceMessage: Boolean(attachment.flags & this.VOICE_FLAG),
|
isVoiceMessage: Boolean(attachment.flags & this.VOICE_FLAG),
|
||||||
thumbnail,
|
thumbnail: thumbnailWithObjectUrl,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getPropsForQuote() {
|
getPropsForQuote() {
|
||||||
|
|
|
@ -1065,27 +1065,24 @@
|
||||||
this.focusMessageField();
|
this.focusMessageField();
|
||||||
},
|
},
|
||||||
|
|
||||||
setQuoteMessage(message) {
|
async setQuoteMessage(message) {
|
||||||
|
this.quote = null;
|
||||||
this.quotedMessage = message;
|
this.quotedMessage = message;
|
||||||
|
|
||||||
|
if (this.quoteHolder) {
|
||||||
|
this.quoteHolder.unload();
|
||||||
|
this.quoteHolder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
const quote = await this.model.makeQuote(this.quotedMessage);
|
||||||
|
console.log({ quote });
|
||||||
|
this.quote = quote;
|
||||||
|
}
|
||||||
|
|
||||||
this.renderQuotedMessage();
|
this.renderQuotedMessage();
|
||||||
},
|
},
|
||||||
|
|
||||||
makeQuote(quotedMessage) {
|
|
||||||
const contact = quotedMessage.getContact();
|
|
||||||
const attachments = quotedMessage.get('attachments');
|
|
||||||
const first = attachments ? attachments[0] : null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
author: contact.id,
|
|
||||||
id: quotedMessage.get('sent_at'),
|
|
||||||
text: quotedMessage.get('body'),
|
|
||||||
attachments: !first ? [] : [{
|
|
||||||
contentType: first.contentType,
|
|
||||||
fileName: first.fileName,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
renderQuotedMessage() {
|
renderQuotedMessage() {
|
||||||
if (this.quoteView) {
|
if (this.quoteView) {
|
||||||
this.quoteView.remove();
|
this.quoteView.remove();
|
||||||
|
@ -1097,9 +1094,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = new Whisper.Message({
|
const message = new Whisper.Message({
|
||||||
quote: this.makeQuote(this.quotedMessage),
|
quote: this.quote,
|
||||||
});
|
});
|
||||||
message.quotedMessage = this.quotedMessage;
|
message.quotedMessage = this.quotedMessage;
|
||||||
|
this.quoteHolder = message;
|
||||||
|
|
||||||
const props = Object.assign({}, message.getPropsForQuote(), {
|
const props = Object.assign({}, message.getPropsForQuote(), {
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
this.setQuoteMessage(null);
|
this.setQuoteMessage(null);
|
||||||
|
|
|
@ -22,6 +22,41 @@
|
||||||
template: i18n('unsupportedFileType')
|
template: i18n('unsupportedFileType')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function makeThumbnail(size, objectUrl) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var img = document.createElement('img');
|
||||||
|
img.onerror = reject;
|
||||||
|
img.onload = function () {
|
||||||
|
// using components/blueimp-load-image
|
||||||
|
|
||||||
|
// first, make the correct size
|
||||||
|
var canvas = loadImage.scale(img, {
|
||||||
|
canvas: true,
|
||||||
|
cover: true,
|
||||||
|
maxWidth: size,
|
||||||
|
maxHeight: size,
|
||||||
|
minWidth: size,
|
||||||
|
minHeight: size,
|
||||||
|
});
|
||||||
|
|
||||||
|
// then crop
|
||||||
|
canvas = loadImage.scale(canvas, {
|
||||||
|
canvas: true,
|
||||||
|
crop: true,
|
||||||
|
maxWidth: size,
|
||||||
|
maxHeight: size,
|
||||||
|
minWidth: size,
|
||||||
|
minHeight: size,
|
||||||
|
});
|
||||||
|
|
||||||
|
var blob = window.dataURLToBlobSync(canvas.toDataURL('image/png'));
|
||||||
|
|
||||||
|
resolve(blob);
|
||||||
|
};
|
||||||
|
img.src = objectUrl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Whisper.FileInputView = Backbone.View.extend({
|
Whisper.FileInputView = Backbone.View.extend({
|
||||||
tagName: 'span',
|
tagName: 'span',
|
||||||
className: 'file-input',
|
className: 'file-input',
|
||||||
|
@ -239,29 +274,11 @@
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
const objectUrl = URL.createObjectURL(file);
|
||||||
var url = URL.createObjectURL(file);
|
return makeThumbnail(256, file).then(function(arrayBuffer) {
|
||||||
var img = document.createElement('img');
|
URL.revokeObjectURL(url);
|
||||||
img.onerror = reject;
|
return this.readFile(arrayBuffer);
|
||||||
img.onload = function () {
|
});
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
// loadImage.scale -> components/blueimp-load-image
|
|
||||||
// scale, then crop.
|
|
||||||
var canvas = loadImage.scale(img, {
|
|
||||||
canvas: true, maxWidth: size, maxHeight: size,
|
|
||||||
cover: true, minWidth: size, minHeight: size
|
|
||||||
});
|
|
||||||
canvas = loadImage.scale(canvas, {
|
|
||||||
canvas: true, maxWidth: size, maxHeight: size,
|
|
||||||
crop: true, minWidth: size, minHeight: size
|
|
||||||
});
|
|
||||||
|
|
||||||
var blob = window.dataURLToBlobSync(canvas.toDataURL('image/png'));
|
|
||||||
|
|
||||||
resolve(blob);
|
|
||||||
};
|
|
||||||
img.src = url;
|
|
||||||
}).then(this.readFile);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// File -> Promise Attachment
|
// File -> Promise Attachment
|
||||||
|
@ -348,4 +365,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Whisper.FileInputView.makeThumbnail = makeThumbnail;
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Add table
Reference in a new issue