diff --git a/js/models/conversations.js b/js/models/conversations.js index db803e89ad0..0d49252185e 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -25,10 +25,13 @@ Contact, Errors, Message, - VisualAttachment, PhoneNumber, } = window.Signal.Types; - const { upgradeMessageSchema, loadAttachmentData } = window.Signal.Migrations; + const { + upgradeMessageSchema, + loadAttachmentData, + getAbsoluteAttachmentPath, + } = window.Signal.Migrations; // TODO: Factor out private and group subclasses of Conversation @@ -771,47 +774,6 @@ 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 { arrayBufferToObjectURL } = Util; - const attachmentWithData = await loadAttachmentData(attachment); - const { data, contentType } = attachmentWithData; - const objectUrl = arrayBufferToObjectURL({ - data, - type: contentType, - }); - - const thumbnail = GoogleChrome.isImageTypeSupported(contentType) - ? await VisualAttachment.makeImageThumbnail(128, objectUrl) - : await VisualAttachment.makeVideoThumbnail(128, objectUrl); - - URL.revokeObjectURL(objectUrl); - - const arrayBuffer = await this.blobToArrayBuffer(thumbnail); - const finalContentType = 'image/png'; - const finalObjectUrl = arrayBufferToObjectURL({ - data: arrayBuffer, - type: finalContentType, - }); - - return { - data: arrayBuffer, - objectUrl: finalObjectUrl, - contentType: finalContentType, - }; - }, - async makeQuote(quotedMessage) { const { getName } = Contact; const contact = quotedMessage.getContact(); @@ -830,29 +792,17 @@ text: body || embeddedContactName, attachments: await Promise.all( (attachments || []).map(async attachment => { - const { contentType } = attachment; - const willMakeThumbnail = - GoogleChrome.isImageTypeSupported(contentType) || - GoogleChrome.isVideoTypeSupported(contentType); - const makeThumbnail = async () => { - try { - if (willMakeThumbnail) { - return await this.makeThumbnailAttachment(attachment); - } - } catch (error) { - console.log( - 'Failed to create quote thumbnail', - error && error.stack ? error.stack : error - ); - } - - return null; - }; + const { contentType, fileName, thumbnail } = attachment; return { contentType, - fileName: attachment.fileName, - thumbnail: await makeThumbnail(), + fileName, + thumbnail: thumbnail + ? { + ...(await loadAttachmentData(thumbnail)), + path: getAbsoluteAttachmentPath(thumbnail.path), + } + : null, }; }) ), @@ -1409,25 +1359,41 @@ return false; } + try { + if ( + queryMessage.get('schemaVersion') < Message.CURRENT_SCHEMA_VERSION + ) { + const upgradedMessage = await upgradeMessageSchema( + queryMessage.attributes + ); + queryMessage.set(upgradedMessage); + await wrapDeferred(message.save()); + } + } catch (error) { + console.log( + 'Problem upgrading message quoted message from database', + Errors.toLogFormat(error) + ); + return false; + } + const queryAttachments = queryMessage.attachments || []; if (queryAttachments.length === 0) { return false; } const queryFirst = queryAttachments[0]; - try { - // eslint-disable-next-line no-param-reassign - message.quoteThumbnail = await this.makeThumbnailAttachment(queryFirst); - return true; - } catch (error) { - console.log( - 'Problem loading attachment data for quoted message from database', - Errors.toLogFormat(error) - ); - return false; - } + const { thumbnail } = queryFirst; + + // eslint-disable-next-line no-param-reassign + message.quoteThumbnail = { + ...thumbnail, + objectUrl: getAbsoluteAttachmentPath(thumbnail.path), + }; + + return true; }, - async loadQuotedMessage(message, quotedMessage) { + loadQuotedMessage(message, quotedMessage) { // eslint-disable-next-line no-param-reassign message.quotedMessage = quotedMessage; @@ -1451,19 +1417,20 @@ return; } - try { - const queryFirst = quotedAttachments[0]; + const queryFirst = quotedAttachments[0]; + const { thumbnail } = queryFirst; - // eslint-disable-next-line no-param-reassign - message.quoteThumbnail = await this.makeThumbnailAttachment(queryFirst); - } catch (error) { - console.log( - 'Problem loading attachment data for quoted message', - error && error.stack ? error.stack : error - ); + if (!thumbnail) { + return; } + + // eslint-disable-next-line no-param-reassign + message.quoteThumbnail = { + ...thumbnail, + objectUrl: getAbsoluteAttachmentPath(thumbnail.path), + }; }, - async loadQuoteThumbnail(message) { + loadQuoteThumbnail(message) { const { quote } = message.attributes; const { attachments } = quote; const first = attachments[0]; @@ -1477,27 +1444,15 @@ if (!thumbnail) { return false; } - try { - const thumbnailWithData = await loadAttachmentData(thumbnail); - const { data, contentType } = thumbnailWithData; - thumbnailWithData.objectUrl = Util.arrayBufferToObjectURL({ - data, - type: contentType, - }); + // If we update this data in place, there's the risk that this data could be + // saved back to the database + // eslint-disable-next-line no-param-reassign + message.quoteThumbnail = { + ...thumbnail, + objectUrl: getAbsoluteAttachmentPath(thumbnail.path), + }; - // If we update this data in place, there's the risk that this data could be - // saved back to the database - // eslint-disable-next-line no-param-reassign - message.quoteThumbnail = thumbnailWithData; - - return true; - } catch (error) { - console.log( - 'loadQuoteThumbnail: had trouble loading thumbnail data from disk', - error && error.stack ? error.stack : error - ); - return false; - } + return true; }, async processQuotes(messages) { const lookup = this.makeMessagesLookup(messages); @@ -1516,7 +1471,10 @@ } // 1. Load provided thumbnail - const gotThumbnail = await this.loadQuoteThumbnail(message, quote); + if (this.loadQuoteThumbnail(message, quote)) { + this.forceRender(message); + return; + } // 2. Check to see if we've already loaded the target message into memory const { author, id } = quote; @@ -1524,13 +1482,7 @@ const quotedMessage = lookup[key]; if (quotedMessage) { - await this.loadQuotedMessage(message, quotedMessage); - this.forceRender(message); - return; - } - - // No need to go further if we already have a thumbnail - if (gotThumbnail) { + this.loadQuotedMessage(message, quotedMessage); this.forceRender(message); return; } @@ -1566,10 +1518,11 @@ const { schemaVersion } = attributes; if (schemaVersion < Message.CURRENT_SCHEMA_VERSION) { - const upgradedMessage = upgradeMessageSchema(attributes); - message.set(upgradedMessage); // Yep, we really do want to wait for each of these // eslint-disable-next-line no-await-in-loop + const upgradedMessage = await upgradeMessageSchema(attributes); + message.set(upgradedMessage); + // eslint-disable-next-line no-await-in-loop await wrapDeferred(message.save()); } } diff --git a/js/models/messages.js b/js/models/messages.js index fce803aa4f0..b5f23a1a528 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -283,12 +283,14 @@ processAttachment(attachment, externalObjectUrl) { const { thumbnail } = attachment; const objectUrl = (thumbnail && thumbnail.objectUrl) || externalObjectUrl; + const path = thumbnail && thumbnail.path; - const thumbnailWithObjectUrl = !objectUrl - ? null - : Object.assign({}, attachment.thumbnail || {}, { - objectUrl, - }); + const thumbnailWithObjectUrl = + !objectUrl && !path + ? null + : Object.assign({}, attachment.thumbnail || {}, { + objectUrl: objectUrl || path, + }); return Object.assign({}, attachment, { isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment), diff --git a/js/views/app_view.js b/js/views/app_view.js index 26420b6b5bd..8e215bf1f79 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -167,7 +167,7 @@ openConversation(conversation) { if (conversation) { this.openInbox().then(() => { - this.inboxView.openConversation(null, conversation); + this.inboxView.openConversation(conversation); }); } }, diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 895e9cba6f5..2c9e743c1f7 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -7,6 +7,7 @@ /* global Signal: false */ /* global storage: false */ /* global Whisper: false */ +/* global wrapDeferred: false */ // eslint-disable-next-line func-names (function() { @@ -14,6 +15,11 @@ window.Whisper = window.Whisper || {}; const { Migrations } = Signal; + const { Message } = window.Signal.Types; + const { + upgradeMessageSchema, + getAbsoluteAttachmentPath, + } = window.Signal.Migrations; Whisper.ExpiredToast = Whisper.ToastView.extend({ render_attributes() { @@ -603,13 +609,29 @@ } ); - // NOTE: Could we show grid previews from disk as well? - const loadMessages = Signal.Components.Types.Message.loadWithObjectURL( - Migrations.loadMessage - ); - const media = await loadMessages(rawMedia); + // First we upgrade these messages to ensure that they have thumbnails + for (let max = rawMedia.length, i = 0; i < max; i += 1) { + const message = rawMedia[i]; + const { schemaVersion } = message; + + if (schemaVersion < Message.CURRENT_SCHEMA_VERSION) { + // Yep, we really do want to wait for each of these + // eslint-disable-next-line no-await-in-loop + rawMedia[i] = await upgradeMessageSchema(message); + const model = new Whisper.Message(rawMedia[i]); + // eslint-disable-next-line no-await-in-loop + await wrapDeferred(model.save()); + } + } + + const media = rawMedia.map(mediaMessage => + Object.assign({}, mediaMessage, { + objectURL: getAbsoluteAttachmentPath( + mediaMessage.attachments[0].path + ), + }) + ); - const { getAbsoluteAttachmentPath } = Signal.Migrations; const saveAttachment = async ({ message } = {}) => { const attachment = message.attachments[0]; const timestamp = message.received_at; @@ -629,13 +651,6 @@ } case 'media': { - const mediaWithObjectURL = media.map(mediaMessage => - Object.assign({}, mediaMessage, { - objectURL: getAbsoluteAttachmentPath( - mediaMessage.attachments[0].path - ), - }) - ); const selectedIndex = media.findIndex( mediaMessage => mediaMessage.id === message.id ); @@ -643,7 +658,7 @@ className: 'lightbox-wrapper', Component: Signal.Components.LightboxGallery, props: { - messages: mediaWithObjectURL, + messages: media, onSave: () => saveAttachment({ message }), selectedIndex, }, @@ -1055,6 +1070,7 @@ }); this.listenBack(view); + this.updateHeader(); }, async openConversation(number) { diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index d8fe04c0de9..1a6fcfb1348 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -247,10 +247,9 @@ } }, openConversation(conversation) { - ConversationController.markAsSelected(conversation); - this.searchView.hideHints(); if (conversation) { + ConversationController.markAsSelected(conversation); this.conversation_stack.open( ConversationController.get(conversation.id) ); diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 40ad1835429..cd5a0d80d15 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -179,7 +179,8 @@ // height calculation. .bottom-bar .quote-wrapper { margin-right: 5px; - margin-bottom: 5px; + margin-bottom: 6px; + margin-top: 3px; } .send .quote-wrapper {