From 31da61a9ac99d0c8b4a5903bd6e21dee06ffc6fb Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Fri, 7 Oct 2016 14:38:47 +0300 Subject: [PATCH] Implement virtual scrolling --- MessageList.js | 53 ++++++++++++++++++++++++++++++++++++++++---------- Store.js | 37 ++++++++++++++++++++++++++++------- Util.js | 21 ++++++++++++++++++++ 3 files changed, 94 insertions(+), 17 deletions(-) diff --git a/MessageList.js b/MessageList.js index 71b10af..a10b48d 100644 --- a/MessageList.js +++ b/MessageList.js @@ -34,17 +34,24 @@ var MessageInList = React.createClass({ // TODO: expand/collapse days var MessageList = React.createClass({ mixins: [ ListWithSelection ], + _preloadSize: 20, + _pageSize: 50, getInitialState: function() { - return { firstDayTop: 0, firstDay: this.props.groups && this.props.groups[0] && this.props.groups[0].name || null, groups: this.props.groups||[] /*FIXME*/ }; + return { + firstDayTop: 0, + firstDay: this.props.groups && this.props.groups[0] && this.props.groups[0].name || null + }; }, componentWillReceiveProps: function(nextProps) { this.setFirstDayFromProps(nextProps); }, + // Main virtual scroll detector method setFirstDayFromProps: function(props) { var groups = props.groups; + var messages = props.messages; if (!groups || !groups.length) return; var scrollTop = this.refs.scroll.scrollTop, scrollSize = this.refs.scroll.offsetHeight - this.getScrollPaddingTop(); @@ -76,6 +83,11 @@ var MessageList = React.createClass({ if (firstVisibleGrp !== undefined && lastVisibleGrp !== undefined) break; } + if (firstVisibleGrp === undefined || firstVisibleGrp >= groups.length) + { + this.refs.scroll.scrollTop = 0; + return; + } if (lastVisibleGrp === undefined) { lastVisibleGrp = groups.length-1; @@ -89,6 +101,23 @@ var MessageList = React.createClass({ lastGrp: lastVisibleGrp, lastMsg: lastVisible }); + var loadFirst = groups[firstVisibleGrp].start+firstVisible-this._preloadSize; + var loadLast = groups[lastVisibleGrp].start+lastVisible+1+this._preloadSize; + var total = groups[groups.length-1].messageCount+groups[groups.length-1].start; + loadLast = Math.floor((loadLast + this._pageSize - 1) / this._pageSize) * this._pageSize; + loadLast = loadLast < total ? loadLast : total; + loadFirst = loadFirst < 0 ? 0 : loadFirst; + loadFirst = loadFirst - (loadFirst % this._pageSize); + var loadFirstEnd; + for (loadFirstEnd = loadFirst; loadFirstEnd < loadLast && messages[loadFirstEnd] === undefined; loadFirstEnd++) + messages[loadFirstEnd] = null; + var loadLastStart; + for (loadLastStart = loadLast; loadLastStart > loadFirst && messages[loadLastStart-1] === undefined; loadLastStart--) + messages[loadLastStart-1] = null; + if (loadFirstEnd > loadFirst) + Store.loadMessages(loadFirst, loadFirstEnd-loadFirst); + if (loadFirstEnd < loadLastStart && loadLastStart < loadLast) + Store.loadMessages(loadLastStart, loadLast-loadLastStart); }, changeFirstDay: function(ev) { @@ -108,16 +137,16 @@ var MessageList = React.createClass({ total += (i > 0 ? 1 : 0)+self.props.groups[i].messageCount; if (index < total) { - idx = index-p-(i > 0 ? 1 : 0); - msg = self.props.groups[i].messages[idx]; - if (!msg.body_text && !msg.body_html) + idx = self.props.groups[i].start+index-p-(i > 0 ? 1 : 0); + msg = self.props.messages[idx]; + if (msg && !msg.body_text && !msg.body_html) { Store.loadMessage(msg.id, function(newMsg) { Store.set('msg', newMsg); - if (self.props.groups[i] && self.props.groups[i].messages[idx] == msg) + if (self.props.messages[idx] == msg) { - self.props.groups[i].messages[idx] = newMsg; + self.props.messages[idx] = newMsg; } }); } @@ -163,7 +192,7 @@ var MessageList = React.createClass({ }, getMessages: function(grp, start, end) { - var a = grp.messages.slice(start, end); + var a = this.props.messages.slice(grp.start+start, grp.start+end); for (var i = 0; i < end-start; i++) if (!a[i]) a[i] = null; @@ -190,13 +219,16 @@ var MessageList = React.createClass({
-
{self.state.firstDay||''}
+
+ {(self.state.firstDay||'').toUpperCase()} +
{(self.props.groups||[]).map(function(grp, i) { if (i > 0) total++; var start = total+(self.state.firstGrp == i ? self.state.firstMsg : 0); var r = [ - i > 0 ?
{grp.name}
: null, + i > 0 ?
{grp.name.toUpperCase()}
: null,
{(self.state.firstGrp > i || self.state.lastGrp < i ?
@@ -238,6 +270,7 @@ module.exports = StoreListener(MessageList, function(data) return { threads: data.threads, layout: data.layout, - groups: data.listGroups + groups: data.listGroups, + messages: data.messages }; }); diff --git a/Store.js b/Store.js index 477475a..f29cc0b 100644 --- a/Store.js +++ b/Store.js @@ -1,6 +1,8 @@ const superagent = require('superagent'); const socket_io = require('socket.io-client'); + const _ = require('./I18n.js'); +const Util = require('./Util.js'); var Store = module.exports = { data: { @@ -9,6 +11,7 @@ var Store = module.exports = { msg: null, threads: false, accounts: [], + listGroups: [], messages: [] }, @@ -136,14 +139,34 @@ var Store = module.exports = { loadFolder: function(folderId) { - superagent.get('backend/messages').query({ folderId: folderId, limit: 50, offset: 0 }).end(function(err, res) + superagent.get('backend/groups').query({ folderId: folderId }).end(function(err, res) { - var msgs = res.body.messages; - Store.set('listGroups', [ { - name: 'TODAY', - messageCount: msgs.length, - messages: msgs - } ]); + var groups = res.body.groups.map(g => { return { name: Util.getGroupName(g.name), messageCount: g.count-0, start: 0 } }); + var start = 0; + for (var i = 0; i < groups.length; i++) + { + groups[i].start = start; + start += groups[i].messageCount; + } + Store.setAll({ + folderId: folderId, + listGroups: groups, + messages: [] + }); + }); + }, + + loadMessages: function(start, count) + { + console.log('load '+start+'..'+count); + superagent.get('backend/messages').query({ folderId: Store.get('folderId'), limit: count, offset: start }).end(function(err, res) + { + var msgs = Store.get('messages').slice(0); + var par = res.body.messages; + par.unshift(par.length); + par.unshift(start); + msgs.splice.apply(msgs, par); + Store.set('messages', msgs); }); }, diff --git a/Util.js b/Util.js index 2422467..cb2a130 100644 --- a/Util.js +++ b/Util.js @@ -35,3 +35,24 @@ module.exports.formatDate = function(dt) var m = dt.getMinutes(); return (h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m; } + +module.exports.getGroupName = function(k) +{ + if (k == 't') + { + return 'Today'; + } + else if (k[0] == 'd') + { + return WeekDays[k.substr(1)%7]; + } + else if (k == 'pw') + { + return 'Last Week'; + } + else if (k[0] == 'm') + { + return Months[k.substr(1)-1]; + } + return k; +}