From 107afcc3fc1e278218fdbed5a1e452d77d9ec6a9 Mon Sep 17 00:00:00 2001
From: Guilherme Gazzo <guilhermegazzo@gmail.com>
Date: Fri, 18 May 2018 19:46:52 -0300
Subject: [PATCH] [NEW] lazy load image attachments (#10608)

[NEW] Lazy load image attachments
---
 .meteor/packages                              |  1 +
 .meteor/versions                              |  1 +
 packages/rocketchat-lazy-load/client/index.js | 58 +++++++++++++++++++
 .../client/lazyloadImage.html                 |  4 ++
 .../client/lazyloadImage.js                   | 21 +++++++
 packages/rocketchat-lazy-load/package.js      | 16 +++++
 .../client/messageAttachment.html             |  4 +-
 .../client/messageAttachment.js               | 25 +-------
 .../rocketchat-message-attachments/package.js |  3 +-
 .../client/imports/general/base_old.css       |  2 -
 .../client/avatar/avatar.html                 |  6 +-
 .../client/avatar/avatar.js                   |  4 +-
 packages/rocketchat-ui-account/package.js     |  3 +-
 .../rocketchat-ui-sidenav/client/sideNav.js   |  5 +-
 .../client/sidebarItem.html                   |  2 +-
 packages/rocketchat-ui-sidenav/package.js     |  3 +-
 .../rocketchat-ui/client/views/app/room.js    | 10 ++++
 packages/rocketchat-ui/package.js             |  3 +-
 18 files changed, 135 insertions(+), 36 deletions(-)
 create mode 100644 packages/rocketchat-lazy-load/client/index.js
 create mode 100644 packages/rocketchat-lazy-load/client/lazyloadImage.html
 create mode 100644 packages/rocketchat-lazy-load/client/lazyloadImage.js
 create mode 100644 packages/rocketchat-lazy-load/package.js

diff --git a/.meteor/packages b/.meteor/packages
index 0cbb854a201..f490350b035 100644
--- a/.meteor/packages
+++ b/.meteor/packages
@@ -190,3 +190,4 @@ rocketchat:version-check
 
 rocketchat:search
 chatpal:search
+rocketchat:lazy-load
diff --git a/.meteor/versions b/.meteor/versions
index d51b6678e43..c71a541b67d 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -174,6 +174,7 @@ rocketchat:internal-hubot@0.0.1
 rocketchat:irc@0.0.2
 rocketchat:issuelinks@0.0.1
 rocketchat:katex@0.0.1
+rocketchat:lazy-load@0.0.1
 rocketchat:ldap@0.0.1
 rocketchat:lib@0.0.1
 rocketchat:livechat@0.0.1
diff --git a/packages/rocketchat-lazy-load/client/index.js b/packages/rocketchat-lazy-load/client/index.js
new file mode 100644
index 00000000000..9b525117e01
--- /dev/null
+++ b/packages/rocketchat-lazy-load/client/index.js
@@ -0,0 +1,58 @@
+import _ from 'underscore';
+import './lazyloadImage';
+export const fixCordova = function(url) {
+	if (url && url.indexOf('data:image') === 0) {
+		return url;
+	}
+	if (Meteor.isCordova && (url && url[0] === '/')) {
+		url = Meteor.absoluteUrl().replace(/\/$/, '') + url;
+		const query = `rc_uid=${ Meteor.userId() }&rc_token=${ Meteor._localStorage.getItem(
+			'Meteor.loginToken'
+		) }`;
+		if (url.indexOf('?') === -1) {
+			url = `${ url }?${ query }`;
+		} else {
+			url = `${ url }&${ query }`;
+		}
+	}
+	if (Meteor.settings['public'].sandstorm || url.match(/^(https?:)?\/\//i)) {
+		return url;
+	} else if (navigator.userAgent.indexOf('Electron') > -1) {
+		return __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url;
+	} else {
+		return Meteor.absoluteUrl().replace(/\/$/, '') + url;
+	}
+};
+
+const loadImage = el => {
+	const img = new Image();
+	const src = el.getAttribute('data-src');
+	el.className = el.className.replace('lazy-img', '');
+	img.onload = function() {
+		el.src = src;
+		el.removeAttribute('data-src');
+	};
+	img.src = fixCordova(src);
+};
+
+const isVisible = el => {
+	requestAnimationFrame(() => {
+		const rect = el.getBoundingClientRect();
+		if (rect.top >= -100 && rect.left >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight)) {
+			return loadImage(el);
+		}
+	});
+
+};
+
+window.addEventListener('resize', window.lazyloadtick);
+
+export const lazyloadtick = _.debounce(() => {
+	[...document.querySelectorAll('.lazy-img[data-src]')]
+
+		.forEach(isVisible);
+}, 500);
+
+window.lazyloadtick = lazyloadtick;
+
+export const addImage = el => isVisible(el);
diff --git a/packages/rocketchat-lazy-load/client/lazyloadImage.html b/packages/rocketchat-lazy-load/client/lazyloadImage.html
new file mode 100644
index 00000000000..c88c6b92762
--- /dev/null
+++ b/packages/rocketchat-lazy-load/client/lazyloadImage.html
@@ -0,0 +1,4 @@
+<template name="lazyloadImage">
+	<img data-src="{{src}}" src="{{lazy}}" height="{{height}}" class="{{class}}"
+	 data-title="{{title}}" data-description="{{description}}">
+</template>
diff --git a/packages/rocketchat-lazy-load/client/lazyloadImage.js b/packages/rocketchat-lazy-load/client/lazyloadImage.js
new file mode 100644
index 00000000000..c8d6a637db0
--- /dev/null
+++ b/packages/rocketchat-lazy-load/client/lazyloadImage.js
@@ -0,0 +1,21 @@
+import './lazyloadImage.html';
+import { addImage, fixCordova } from './';
+
+Template.lazyloadImage.helpers({
+	lazy() {
+		const { preview, src, placeholder } = this;
+
+		if (!preview && !placeholder) {
+			return fixCordova(src);
+		}
+		return `data:image/png;base64,${ preview || 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8+/u3PQAJJAM0dIyWdgAAAABJRU5ErkJggg==' }`;
+	}
+});
+
+Template.lazyloadImage.onCreated(function() {
+	const element = Template.instance().firstNode;
+	if (!element) {
+		return;
+	}
+	addImage(element);
+});
diff --git a/packages/rocketchat-lazy-load/package.js b/packages/rocketchat-lazy-load/package.js
new file mode 100644
index 00000000000..d65e3f99cb5
--- /dev/null
+++ b/packages/rocketchat-lazy-load/package.js
@@ -0,0 +1,16 @@
+Package.describe({
+	name: 'rocketchat:lazy-load',
+	version: '0.0.1',
+	summary: 'Lazy load image',
+	git: ''
+});
+
+Package.onUse(function(api) {
+	api.use([
+		'ecmascript',
+		'templating',
+		'rocketchat:lib'
+	]);
+
+	api.mainModule('client/index.js', 'client');
+});
diff --git a/packages/rocketchat-message-attachments/client/messageAttachment.html b/packages/rocketchat-message-attachments/client/messageAttachment.html
index 4c3879a32c0..6bc61ddd064 100644
--- a/packages/rocketchat-message-attachments/client/messageAttachment.html
+++ b/packages/rocketchat-message-attachments/client/messageAttachment.html
@@ -66,9 +66,7 @@
 					<div class="attachment-image">
 					{{#if loadImage}}
 						<figure>
-							<div class="inline-image" style="background-image: url('{{fixCordova image_url}}');">
-								<img src="{{fixCordova image_url}}" height="{{getImageHeight image_dimensions.height}}" class="gallery-item" data-title="{{title}}" data-description="{{description}}">
-							</div>
+							{{> lazyloadImage src=image_url preview=image_preview height=(getImageHeight image_dimensions.height) class="lazy-img gallery-item" title=title description=description}}
 							{{#if labels}}
 								<div class="image-labels">
 									{{#each labels}}
diff --git a/packages/rocketchat-message-attachments/client/messageAttachment.js b/packages/rocketchat-message-attachments/client/messageAttachment.js
index 69680886f5c..37acc16c00b 100644
--- a/packages/rocketchat-message-attachments/client/messageAttachment.js
+++ b/packages/rocketchat-message-attachments/client/messageAttachment.js
@@ -1,30 +1,11 @@
 import moment from 'moment';
+import { fixCordova } from 'meteor/rocketchat:lazy-load';
 const colors = {
 	good: '#35AC19',
 	warning: '#FCB316',
 	danger: '#D30230'
 };
-const fixCordova = function(url) {
-	if (url && url.indexOf('data:image') === 0) {
-		return url;
-	}
-	if (Meteor.isCordova && (url && url[0] === '/')) {
-		url = Meteor.absoluteUrl().replace(/\/$/, '') + url;
-		const query = `rc_uid=${ Meteor.userId() }&rc_token=${ Meteor._localStorage.getItem('Meteor.loginToken') }`;
-		if (url.indexOf('?') === -1) {
-			url = `${ url }?${ query }`;
-		} else {
-			url = `${ url }&${ query }`;
-		}
-	}
-	if (Meteor.settings['public'].sandstorm || url.match(/^(https?:)?\/\//i)) {
-		return url;
-	} else if (navigator.userAgent.indexOf('Electron') > -1) {
-		return __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url;
-	} else {
-		return Meteor.absoluteUrl().replace(/\/$/, '') + url;
-	}
-};
+
 /*globals renderMessageBody*/
 Template.messageAttachment.helpers({
 	fixCordova,
@@ -34,8 +15,8 @@ Template.messageAttachment.helpers({
 		});
 	},
 	loadImage() {
-		const user = Meteor.user();
 		if (this.downloadImages !== true) {
+			const user = RocketChat.models.Users.findOne({_id: Meteor.userId()}, {fields: {'settings.autoImageLoad' : 1}});
 			if (RocketChat.getUserPreference(user, 'autoImageLoad') === false) {
 				return false;
 			}
diff --git a/packages/rocketchat-message-attachments/package.js b/packages/rocketchat-message-attachments/package.js
index eaaa2c47f6f..571487cf2b4 100644
--- a/packages/rocketchat-message-attachments/package.js
+++ b/packages/rocketchat-message-attachments/package.js
@@ -9,7 +9,8 @@ Package.onUse(function(api) {
 	api.use([
 		'templating',
 		'ecmascript',
-		'rocketchat:lib'
+		'rocketchat:lib',
+		'rocketchat:lazy-load'
 	]);
 
 	api.addFiles('client/messageAttachment.html', 'client');
diff --git a/packages/rocketchat-theme/client/imports/general/base_old.css b/packages/rocketchat-theme/client/imports/general/base_old.css
index 48e69f558aa..208c7b6f46e 100644
--- a/packages/rocketchat-theme/client/imports/general/base_old.css
+++ b/packages/rocketchat-theme/client/imports/general/base_old.css
@@ -3167,8 +3167,6 @@
 				max-height: 200px;
 
 				cursor: pointer;
-
-				opacity: 0;
 			}
 		}
 
diff --git a/packages/rocketchat-ui-account/client/avatar/avatar.html b/packages/rocketchat-ui-account/client/avatar/avatar.html
index 70781f54c7b..de8f529657f 100644
--- a/packages/rocketchat-ui-account/client/avatar/avatar.html
+++ b/packages/rocketchat-ui-account/client/avatar/avatar.html
@@ -1,5 +1,9 @@
 <template name="avatar">
 	<div class="avatar">
-		<div class="avatar-image" style="{{imageUrl}}"></div>
+		{{# if lazy}}
+			{{> lazyloadImage src=src class="avatar-image lazy-img" placeholder=true}}
+		{{else}}
+			<img src="{{src}}" class="avatar-image"/>
+		{{/if}}
 	</div>
 </template>
diff --git a/packages/rocketchat-ui-account/client/avatar/avatar.js b/packages/rocketchat-ui-account/client/avatar/avatar.js
index 555bf769cd7..27e00392a8c 100644
--- a/packages/rocketchat-ui-account/client/avatar/avatar.js
+++ b/packages/rocketchat-ui-account/client/avatar/avatar.js
@@ -1,5 +1,5 @@
 Template.avatar.helpers({
-	imageUrl() {
+	src() {
 		let {url} = Template.instance().data;
 		if (!url) {
 			let username = this.username;
@@ -18,6 +18,6 @@ Template.avatar.helpers({
 
 			url = getAvatarUrlFromUsername(username);
 		}
-		return `background-image:url(${ url });`;
+		return url;
 	}
 });
diff --git a/packages/rocketchat-ui-account/package.js b/packages/rocketchat-ui-account/package.js
index 2d8ac01d4fa..fbdc651699a 100644
--- a/packages/rocketchat-ui-account/package.js
+++ b/packages/rocketchat-ui-account/package.js
@@ -15,7 +15,8 @@ Package.onUse(function(api) {
 		'ecmascript',
 		'templating',
 		'rocketchat:lib',
-		'sha'
+		'sha',
+		'rocketchat:lazy-load'
 	]);
 
 	api.addFiles('client/account.html', 'client');
diff --git a/packages/rocketchat-ui-sidenav/client/sideNav.js b/packages/rocketchat-ui-sidenav/client/sideNav.js
index f360e188322..fb5ba7d0c72 100644
--- a/packages/rocketchat-ui-sidenav/client/sideNav.js
+++ b/packages/rocketchat-ui-sidenav/client/sideNav.js
@@ -1,3 +1,5 @@
+import { lazyloadtick } from 'meteor/rocketchat:lazy-load';
+
 /* globals menu*/
 
 Template.sideNav.helpers({
@@ -51,6 +53,7 @@ Template.sideNav.events({
 	},
 
 	'scroll .rooms-list'() {
+		lazyloadtick();
 		return menu.updateUnreadBars();
 	},
 
@@ -62,7 +65,7 @@ Template.sideNav.events({
 Template.sideNav.onRendered(function() {
 	SideNav.init();
 	menu.init();
-
+	lazyloadtick();
 	const first_channel_login = RocketChat.settings.get('First_Channel_After_Login');
 	const room = RocketChat.roomTypes.findRoom('c', first_channel_login, Meteor.userId());
 	if (room !== undefined && room._id !== '') {
diff --git a/packages/rocketchat-ui-sidenav/client/sidebarItem.html b/packages/rocketchat-ui-sidenav/client/sidebarItem.html
index b527528cf39..54404f12dbe 100644
--- a/packages/rocketchat-ui-sidenav/client/sidebarItem.html
+++ b/packages/rocketchat-ui-sidenav/client/sidebarItem.html
@@ -11,7 +11,7 @@
 						{{/if}}
 					{{else}}
 						<div class="sidebar-item__user-thumb">
-							{{> avatar username=username roomIcon=icon}}
+							{{> avatar username=username roomIcon=icon lazy=true}}
 						</div>
 					{{/if}}
 				</div>
diff --git a/packages/rocketchat-ui-sidenav/package.js b/packages/rocketchat-ui-sidenav/package.js
index 752ba50ba53..4345f9bb5ec 100644
--- a/packages/rocketchat-ui-sidenav/package.js
+++ b/packages/rocketchat-ui-sidenav/package.js
@@ -15,7 +15,8 @@ Package.onUse(function(api) {
 		'ecmascript',
 		'templating',
 		'rocketchat:lib',
-		'rocketchat:ui'
+		'rocketchat:ui',
+		'rocketchat:lazy-load'
 	]);
 
 	api.addFiles('client/createCombinedFlex.html', 'client');
diff --git a/packages/rocketchat-ui/client/views/app/room.js b/packages/rocketchat-ui/client/views/app/room.js
index 7fe7de467fa..7fbdfc3fe6a 100644
--- a/packages/rocketchat-ui/client/views/app/room.js
+++ b/packages/rocketchat-ui/client/views/app/room.js
@@ -6,6 +6,8 @@ import moment from 'moment';
 import mime from 'mime-type/with-db';
 import Clipboard from 'clipboard';
 
+import { lazyloadtick } from 'meteor/rocketchat:lazy-load';
+
 window.chatMessages = window.chatMessages || {};
 const isSubscribed = _id => ChatSubscription.find({ rid: _id }).count() > 0;
 
@@ -536,6 +538,9 @@ Template.room.events({
 	},
 
 	'scroll .wrapper': _.throttle(function(e, t) {
+
+		lazyloadtick();
+
 		const $roomLeader = $('.room-leader');
 		if ($roomLeader.length) {
 			if (e.target.scrollTop < lastScrollTop) {
@@ -739,6 +744,9 @@ Template.room.events({
 Template.room.onCreated(function() {
 	// this.scrollOnBottom = true
 	// this.typing = new msgTyping this.data._id
+
+	lazyloadtick();
+
 	this.showUsersOffline = new ReactiveVar(false);
 	this.atBottom = FlowRouter.getQueryParam('msg') ? false : true;
 	this.unreadCount = new ReactiveVar(0);
@@ -898,6 +906,8 @@ Template.room.onRendered(function() {
 		if (template.atBottom === true && template.isAtBottom() !== true) {
 			template.sendToBottom();
 		}
+
+		lazyloadtick();
 	};
 
 	template.sendToBottomIfNecessaryDebounced = _.debounce(template.sendToBottomIfNecessary, 10);
diff --git a/packages/rocketchat-ui/package.js b/packages/rocketchat-ui/package.js
index 68782079277..343cd8152fe 100644
--- a/packages/rocketchat-ui/package.js
+++ b/packages/rocketchat-ui/package.js
@@ -23,7 +23,8 @@ Package.onUse(function(api) {
 		'rocketchat:lib',
 		'rocketchat:ui-master',
 		'raix:push',
-		'raix:ui-dropped-event'
+		'raix:ui-dropped-event',
+		'rocketchat:lazy-load'
 	]);
 
 	api.use('kadira:flow-router', 'client');
-- 
GitLab