Implement virtual scrolling

master
Vitaliy Filippov 2016-10-07 14:38:47 +03:00
parent 4ea0bdcb85
commit 31da61a9ac
3 changed files with 94 additions and 17 deletions

View File

@ -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({
<div className="clear"></div>
</div>
<div ref="scroll" className="listview" tabIndex="1" onScroll={self.changeFirstDay} onKeyDown={self.onListKeyDown}>
<div ref="title" className="day first-day" style={{ top: self.state.firstDayTop, display: self.state.firstDay ? '' : 'none' }}>{self.state.firstDay||''}</div>
<div ref="title" className="day first-day"
style={{ top: self.state.firstDayTop, display: self.state.firstDay ? '' : 'none' }}>
{(self.state.firstDay||'').toUpperCase()}
</div>
{(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 ? <div className="day" data-i={total-1}>{grp.name}</div> : null,
i > 0 ? <div className="day" data-i={total-1}>{grp.name.toUpperCase()}</div> : null,
<div className="day-list">
{(self.state.firstGrp > i || self.state.lastGrp < i
? <div className="placeholder" style={{ height: itemH*grp.messageCount }}></div>
@ -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
};
});

View File

@ -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);
});
},

21
Util.js
View File

@ -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;
}