Fix threads rendering performance (#14059)

Fix threads rendering performance
pull/14086/head^2
Guilherme Gazzo 6 years ago committed by Tasso Evangelista
parent 9dffb8e458
commit f8bde388ea
  1. 8
      app/authorization/client/hasPermission.js
  2. 2
      app/models/client/models/Subscriptions.js
  3. 4
      app/threads/client/flextab/thread.html
  4. 2
      app/threads/client/flextab/thread.js
  5. 13
      app/threads/client/flextab/threads.html
  6. 36
      app/threads/client/flextab/threads.js
  7. 39
      app/ui-message/client/message.js
  8. 3
      app/ui/client/views/app/room.js

@ -5,11 +5,11 @@ import { ChatPermissions } from './lib/ChatPermissions';
function atLeastOne(permissions = [], scope) { function atLeastOne(permissions = [], scope) {
return permissions.some((permissionId) => { return permissions.some((permissionId) => {
const permission = ChatPermissions.findOne(permissionId); const permission = ChatPermissions.findOne(permissionId, { fields: { roles: 1 } });
const roles = (permission && permission.roles) || []; const roles = (permission && permission.roles) || [];
return roles.some((roleName) => { return roles.some((roleName) => {
const role = Models.Roles.findOne(roleName); const role = Models.Roles.findOne(roleName, { fields: { scope: 1 } });
const roleScope = role && role.scope; const roleScope = role && role.scope;
const model = Models[roleScope]; const model = Models[roleScope];
@ -20,11 +20,11 @@ function atLeastOne(permissions = [], scope) {
function all(permissions = [], scope) { function all(permissions = [], scope) {
return permissions.every((permissionId) => { return permissions.every((permissionId) => {
const permission = ChatPermissions.findOne(permissionId); const permission = ChatPermissions.findOne(permissionId, { fields: { roles: 1 } });
const roles = (permission && permission.roles) || []; const roles = (permission && permission.roles) || [];
return roles.some((roleName) => { return roles.some((roleName) => {
const role = Models.Roles.findOne(roleName); const role = Models.Roles.findOne(roleName, { fields: { scope: 1 } });
const roleScope = role && role.scope; const roleScope = role && role.scope;
const model = Models[roleScope]; const model = Models[roleScope];

@ -14,7 +14,7 @@ Object.assign(Subscriptions, {
rid: roomId, rid: roomId,
}; };
const subscription = this.findOne(query); const subscription = this.findOne(query, { fields: { roles: 1 } });
return subscription && Array.isArray(subscription.roles) && subscription.roles.includes(roleName); return subscription && Array.isArray(subscription.roles) && subscription.roles.includes(roleName);
}, { maxAge: 1000 }), }, { maxAge: 1000 }),

@ -14,9 +14,9 @@
<div class="thread-list js-scroll-thread"> <div class="thread-list js-scroll-thread">
<ul class="thread"> <ul class="thread">
{{# with messageContext}} {{# with messageContext}}
{{> nrr nrrargs 'message' msg=mainMessage room=room subscription=subscription settings=settings customClass="thread-message" templatePrefix='thread-' customClass="thread-main" u=u}} {{> nrr nrrargs 'message' hideRoles=true msg=mainMessage room=room subscription=subscription settings=settings customClass="thread-message" templatePrefix='thread-' customClass="thread-main" u=u}}
{{#each msg in messages}} {{#each msg in messages}}
{{> nrr nrrargs 'message' msg=msg room=room subscription=subscription settings=settings templatePrefix='thread-' u=u}} {{> nrr nrrargs 'message' hideRoles=true msg=msg room=room subscription=subscription settings=settings templatePrefix='thread-' u=u}}
{{/each}} {{/each}}
{{/with}} {{/with}}
</ul> </ul>

@ -95,7 +95,7 @@ Template.thread.onRendered(function() {
const tmid = this.state.get('tmid'); const tmid = this.state.get('tmid');
this.threadsObserve && this.threadsObserve.stop(); this.threadsObserve && this.threadsObserve.stop();
this.threadsObserve = Messages.find({ tmid }).observe({ this.threadsObserve = Messages.find({ tmid, _updatedAt: { $gt: new Date() } }).observe({
added: ({ _id, ...message }) => { added: ({ _id, ...message }) => {
const { atBottom } = this; const { atBottom } = this;
this.Threads.upsert({ _id }, message); this.Threads.upsert({ _id }, message);

@ -1,8 +1,9 @@
<template name="threads"> <template name="threads">
{{#unless hasThreads}} {{#if hasNoThreads}}
<h2 class="thread-empty">{{_ "No_Threads"}}</h2> <h2 class="thread-empty">{{_ "No_Threads"}}</h2>
{{/unless}} {{/if}}
{{# with messageContext}} {{# with messageContext}}
{{#unless doDotLoadThreads}}
<div class="thread-list js-scroll-threads"> <div class="thread-list js-scroll-threads">
<ul class="thread"> <ul class="thread">
{{#each thread in threads}} {{#each thread in threads}}
@ -10,6 +11,7 @@
groupable=false groupable=false
msg=thread msg=thread
room=room room=room
hideRoles=true
subscription=subscription subscription=subscription
customClass="thread-message" customClass="thread-message"
settings=settings settings=settings
@ -22,9 +24,12 @@
{{/each}} {{/each}}
</ul> </ul>
</div> </div>
<div class="rc-user-info-container flex-nav animated{{#unless message}} animated-hidden{{/unless}}"> {{/unless}}
{{#if message}} {{> thread mainMessage=message room=room subscription=subscription settings=settings close=close}} {{/if}} {{#if message}}
<div class="rc-user-info-container flex-nav">
{{> thread mainMessage=message room=room subscription=subscription settings=settings close=close}}
</div> </div>
{{/if}}
{{/with}} {{/with}}
{{#if isLoading}} {{#if isLoading}}
<div class="load-more"> <div class="load-more">

@ -35,10 +35,13 @@ Template.threads.events({
}); });
Template.threads.helpers({ Template.threads.helpers({
doDotLoadThreads() {
return Template.instance().state.get('close');
},
close() { close() {
const instance = Template.instance(); const { state, data } = Template.instance();
const { tabBar } = instance.data; const { tabBar } = data;
return () => (instance.close ? tabBar.close() : instance.state.set('mid', null)); return () => (state.get('close') ? tabBar.close() : state.set('mid', null));
}, },
message() { message() {
return Template.instance().state.get('thread'); return Template.instance().state.get('thread');
@ -46,8 +49,8 @@ Template.threads.helpers({
isLoading() { isLoading() {
return Template.instance().state.get('loading'); return Template.instance().state.get('loading');
}, },
hasThreads() { hasNoThreads() {
return Template.instance().Threads.find({ rid: Template.instance().state.get('rid') }, { sort }).count(); return !Template.instance().state.get('loading') && Template.instance().Threads.find({ rid: Template.instance().state.get('rid') }, { sort }).count() === 0;
}, },
threads() { threads() {
return Template.instance().Threads.find({ rid: Template.instance().state.get('rid') }, { sort, limit: Template.instance().state.get('limit') }); return Template.instance().Threads.find({ rid: Template.instance().state.get('rid') }, { sort, limit: Template.instance().state.get('limit') });
@ -56,17 +59,19 @@ Template.threads.helpers({
}); });
Template.threads.onCreated(async function() { Template.threads.onCreated(async function() {
this.Threads = new Mongo.Collection(null);
const { rid, mid, msg } = this.data;
this.state = new ReactiveDict({ this.state = new ReactiveDict({
rid: this.data.rid, rid,
close: !!mid,
loading: true, loading: true,
mid,
thread: msg,
}); });
this.Threads = new Mongo.Collection(null);
this.incLimit = () => { this.incLimit = () => {
if (this.state.get('loading')) {
return;
}
const { rid, limit } = Tracker.nonreactive(() => this.state.all()); const { rid, limit } = Tracker.nonreactive(() => this.state.all());
const count = this.Threads.find({ rid }).count(); const count = this.Threads.find({ rid }).count();
@ -80,13 +85,13 @@ Template.threads.onCreated(async function() {
}; };
this.loadMore = _.debounce(async () => { this.loadMore = _.debounce(async () => {
if (this.state.get('loading')) { const { rid, limit } = Tracker.nonreactive(() => this.state.all());
if (this.state.get('loading') === rid) {
return; return;
} }
const { rid, limit } = Tracker.nonreactive(() => this.state.all());
this.state.set('loading', true); this.state.set('loading', rid);
const threads = await call('getThreadsList', { rid, limit: LIST_SIZE, skip: limit - LIST_SIZE }); const threads = await call('getThreadsList', { rid, limit: LIST_SIZE, skip: limit - LIST_SIZE });
upsert(this.Threads, threads); upsert(this.Threads, threads);
// threads.forEach(({ _id, ...msg }) => this.Threads.upsert({ _id }, msg)); // threads.forEach(({ _id, ...msg }) => this.Threads.upsert({ _id }, msg));
@ -97,9 +102,9 @@ Template.threads.onCreated(async function() {
Tracker.afterFlush(() => { Tracker.afterFlush(() => {
this.autorun(async () => { this.autorun(async () => {
const { rid, mid } = Template.currentData(); const { rid, mid } = Template.currentData();
this.close = !!mid;
this.state.set({ this.state.set({
close: !!mid,
mid, mid,
rid, rid,
}); });
@ -111,7 +116,6 @@ Template.threads.onCreated(async function() {
this.rid = rid; this.rid = rid;
this.state.set({ this.state.set({
limit: LIST_SIZE, limit: LIST_SIZE,
loading: false,
}); });
this.loadMore(); this.loadMore();
}); });
@ -119,7 +123,7 @@ Template.threads.onCreated(async function() {
this.autorun(() => { this.autorun(() => {
const rid = this.state.get('rid'); const rid = this.state.get('rid');
this.threadsObserve && this.threadsObserve.stop(); this.threadsObserve && this.threadsObserve.stop();
this.threadsObserve = Messages.find({ rid, tcount: { $exists: true } }).observe({ this.threadsObserve = Messages.find({ rid, _updatedAt: { $gt: new Date() }, tcount: { $exists: true } }).observe({
added: ({ _id, ...message }) => { added: ({ _id, ...message }) => {
this.Threads.upsert({ _id }, message); this.Threads.upsert({ _id }, message);
}, // Update message to re-render DOM }, // Update message to re-render DOM

@ -14,6 +14,7 @@ import { AutoTranslate } from '../../autotranslate/client';
import { callbacks } from '../../callbacks/client'; import { callbacks } from '../../callbacks/client';
import { Markdown } from '../../markdown/client'; import { Markdown } from '../../markdown/client';
import { t, roomTypes, getURL } from '../../utils'; import { t, roomTypes, getURL } from '../../utils';
import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
async function renderPdfToCanvas(canvasId, pdfLink) { async function renderPdfToCanvas(canvasId, pdfLink) {
const isSafari = /constructor/i.test(window.HTMLElement) || const isSafari = /constructor/i.test(window.HTMLElement) ||
@ -90,8 +91,8 @@ Template.message.helpers({
return encodeURI(text); return encodeURI(text);
}, },
broadcast() { broadcast() {
const { msg, room = {} } = this; const { msg, room = {}, u } = this;
return !msg.private && !msg.t && msg.u._id !== Meteor.userId() && room && room.broadcast; return !msg.private && !msg.t && msg.u._id !== u._id && room && room.broadcast;
}, },
isIgnored() { isIgnored() {
const { msg } = this; const { msg } = this;
@ -145,8 +146,8 @@ Template.message.helpers({
} }
}, },
sequentialClass() { sequentialClass() {
const { msg } = this; const { msg, groupable } = this;
return msg.groupable !== false && 'sequential'; return groupable !== false && msg.groupable !== false && 'sequential';
}, },
avatarFromUsername() { avatarFromUsername() {
const { msg } = this; const { msg } = this;
@ -217,8 +218,8 @@ Template.message.helpers({
} }
}, },
showTranslated() { showTranslated() {
const { msg, subscription, settings } = this; const { msg, subscription, settings, u } = this;
if (settings.AutoTranslate_Enabled && msg.u && msg.u._id !== Meteor.userId() && !MessageTypes.isSystemMessage(msg)) { if (settings.AutoTranslate_Enabled && msg.u && msg.u._id !== u._id && !MessageTypes.isSystemMessage(msg)) {
const language = AutoTranslate.getLanguage(msg.rid); const language = AutoTranslate.getLanguage(msg.rid);
const autoTranslate = subscription && subscription.autoTranslate; const autoTranslate = subscription && subscription.autoTranslate;
return msg.autoTranslateFetching || (!!autoTranslate !== !!msg.autoTranslateShowInverse && msg.translations && msg.translations[language]); return msg.autoTranslateFetching || (!!autoTranslate !== !!msg.autoTranslateShowInverse && msg.translations && msg.translations[language]);
@ -266,8 +267,7 @@ Template.message.helpers({
return true; return true;
}, },
reactions() { reactions() {
const { username: myUsername, name: myName } = Meteor.user() || {}; const { msg: { reactions = {} }, u: { username: myUsername, name: myName } } = this;
const { msg: { reactions = {} } } = this;
return Object.entries(reactions) return Object.entries(reactions)
.map(([emoji, reaction]) => { .map(([emoji, reaction]) => {
@ -473,11 +473,8 @@ const getPreviousSentMessage = (currentNode) => {
}; };
const setNewDayAndGroup = (currentNode, previousNode, forceDate, period, noDate) => { const setNewDayAndGroup = (currentNode, previousNode, forceDate, period, noDate) => {
const { classList } = currentNode; const { classList } = currentNode;
// const $nextNode = $(nextNode);
if (previousNode == null) { if (previousNode == null) {
classList.remove('sequential'); classList.remove('sequential');
@ -493,7 +490,6 @@ const setNewDayAndGroup = (currentNode, previousNode, forceDate, period, noDate)
classList.add('new-day'); classList.add('new-day');
} }
if (previousDataset.tmid !== currentDataset.tmid) { if (previousDataset.tmid !== currentDataset.tmid) {
return classList.remove('sequential'); return classList.remove('sequential');
} }
@ -508,15 +504,16 @@ const setNewDayAndGroup = (currentNode, previousNode, forceDate, period, noDate)
}; };
Template.message.onViewRendered = function(context) { Template.message.onViewRendered = function() {
const [, currentData] = Template.currentData()._arguments; const { settings, forceDate, noDate, groupable, msg } = messageArgs(Template.currentData());
const { settings, forceDate, noDate, msg } = currentData.hash;
if (msg.tmid && !msg.threadMsg) { if (noDate && !groupable) {
findParentMessage(msg.tmid); return;
} }
return this._domrange.onAttached((domRange) => { return this._domrange.onAttached((domRange) => {
if (context.file && context.file.type === 'application/pdf') { if (msg.file && msg.file.type === 'application/pdf') {
Meteor.defer(() => { renderPdfToCanvas(context.file._id, context.attachments[0].title_link); }); Meteor.defer(() => { renderPdfToCanvas(msg.file._id, msg.attachments[0].title_link); });
} }
const currentNode = domRange.lastNode(); const currentNode = domRange.lastNode();
const currentDataset = currentNode.dataset; const currentDataset = currentNode.dataset;
@ -541,7 +538,7 @@ Template.message.onViewRendered = function(context) {
} }
} }
} else { } else {
const [el] = $(`#chat-window-${ context.rid }`); const [el] = $(`#chat-window-${ msg.rid }`);
const view = el && Blaze.getView(el); const view = el && Blaze.getView(el);
const templateInstance = view && view.templateInstance(); const templateInstance = view && view.templateInstance();
if (!templateInstance) { if (!templateInstance) {
@ -553,7 +550,5 @@ Template.message.onViewRendered = function(context) {
} }
templateInstance.sendToBottomIfNecessary(); templateInstance.sendToBottomIfNecessary();
} }
}); });
}; };

@ -513,11 +513,12 @@ Template.room.events({
'click .js-open-thread'() { 'click .js-open-thread'() {
const { tabBar } = Template.instance(); const { tabBar } = Template.instance();
const { msg: { rid, _id, tmid } } = messageArgs(this); const { msg, msg: { rid, _id, tmid } } = messageArgs(this);
const $flexTab = $('.flex-tab-container .flex-tab'); const $flexTab = $('.flex-tab-container .flex-tab');
$flexTab.attr('template', 'thread'); $flexTab.attr('template', 'thread');
tabBar.setData({ tabBar.setData({
msg,
rid, rid,
mid: tmid || _id, mid: tmid || _id,
label: 'Threads', label: 'Threads',

Loading…
Cancel
Save