Merge "Virtual scroll!"

This commit is contained in:
Aaron Boodman 2014-01-20 19:12:13 +00:00 committed by Gerrit Code Review
commit fc62c5c2b4
1 changed files with 69 additions and 22 deletions

View File

@ -63,29 +63,32 @@ cam.BlobItemContainerReact = React.createClass({
this.lastCheckedIndex_ = -1;
this.scrollbarWidth_ = goog.style.getScrollbarWidth();
this.layoutHeight_ = 0;
this.childProps_ = null;
this.lastSize_ = new goog.math.Size(this.props.style.width, this.props.style.height);
this.updateChildProps_();
},
componentDidMount: function() {
this.eh_.listen(this.props.searchSession, cam.SearchSession.SEARCH_SESSION_CHANGED, this.handleSearchSessionChanged_);
this.eh_.listen(this.getDOMNode(), 'scroll', this.handleScroll_);
if (this.props.history.state && this.props.history.state.scroll) {
var el = this.getDOMNode();
var oldScroll = el.scrollTop;
el.scrollTop = this.props.history.state.scroll;
this.props.history.replaceState({scroll:0});
if (oldScroll != el.scrollTop) {
return;
}
this.getDOMNode().scrollTop = this.props.history.state.scroll;
}
// If we weren't able to scroll to a remembered position, call handleScroll once anyway to scroll to zero. This will cause us to load our initial data.
this.handleScroll_();
this.fillVisibleAreaWithResults_();
},
componentWillReceiveProps: function(nextProps) {
if (nextProps.searchSession != this.props.searchSession) {
this.eh_.unlisten(this.props.searchSession, cam.SearchSession.SEARCH_SESSION_CHANGED, this.handleSearchSessionChanged_);
this.eh_.listen(nextProps.searchSession, cam.SearchSession.SEARCH_SESSION_CHANGED, this.handleSearchSessionChanged_);
nextProps.searchSession.loadMoreResults();
}
var nextSize = new goog.math.Size(nextProps.style.width, nextProps.style.height);
if (nextProps.searchSession != this.props.searchSession || !goog.math.Size.equals(this.lastSize_, nextSize)) {
this.lastSize_ = nextSize;
this.updateChildProps_();
}
},
@ -93,9 +96,41 @@ cam.BlobItemContainerReact = React.createClass({
this.eh_.dispose();
},
getInitialState: function() {
return {
scroll:0,
};
},
render: function() {
var results = this.props.searchSession.getCurrentResults();
var children = [];
this.childProps_.forEach(function(props) {
if (this.isVisible_(props.position.y) || this.isVisible_(props.position.y + props.size.height)) {
children.push(cam.BlobItemReact(props));
}
}.bind(this));
children.push(React.DOM.div({
key: 'marker',
style: {
position: 'absolute',
top: this.layoutHeight_ - 1,
left: 0,
height: 1,
width: 1,
},
}));
// If we haven't filled the window with results, add some more.
this.fillVisibleAreaWithResults_();
return React.DOM.div({className:'cam-blobitemcontainer', style:this.props.style, onMouseDown:this.handleMouseDown_}, children);
},
updateChildProps_: function() {
this.childProps_ = [];
var results = this.props.searchSession.getCurrentResults();
var data = goog.array.map(results.blobs, function(blob) {
return new cam.BlobItemReactData(blob.blob, results.description.meta);
});
@ -130,27 +165,23 @@ cam.BlobItemContainerReact = React.createClass({
rowWidth = nextWidth;
}
currentTop += this.renderChildren_(data, children, rowStart, rowEnd, availWidth, rowWidth, currentTop) + this.BLOB_ITEM_MARGIN_;
currentTop += this.updateChildPropsRow_(data, rowStart, rowEnd, availWidth, rowWidth, currentTop) + this.BLOB_ITEM_MARGIN_;
currentWidth = this.BLOB_ITEM_MARGIN_;
rowStart = rowEnd + 1;
i = rowEnd;
}
// If we haven't filled the window with results, add some more.
this.layoutHeight_ = currentTop;
return React.DOM.div({className:'cam-blobitemcontainer', style:this.props.style, onMouseDown:this.handleMouseDown_}, children);
},
renderChildren_: function(data, children, startIndex, endIndex, availWidth, usedWidth, top) {
updateChildPropsRow_: function(data, startIndex, endIndex, availWidth, usedWidth, top) {
var currentLeft = 0;
var rowHeight = Number.POSITIVE_INFINITY;
var numItems = endIndex - startIndex + 1;
var availThumbWidth = availWidth - (this.BLOB_ITEM_MARGIN_ * (numItems + 1));
var usedThumbWidth = usedWidth - (this.BLOB_ITEM_MARGIN_ * (numItems + 1));
var rowProps = [];
for (var i = startIndex; i <= endIndex; i++) {
// We figure out the amount to adjust each item in this slightly non-intuitive way so that the adjustment is split up as fairly as possible. Figuring out a ratio up front and applying it to all items uniformly can end up with a large amount left over because of rounding.
@ -162,7 +193,7 @@ cam.BlobItemContainerReact = React.createClass({
var ratio = width / originalWidth;
var height = Math.round(this.props.thumbnailSize * ratio);
rowProps.push({
this.childProps_.push({
key: item.blobref,
blobref: item.blobref,
checked: Boolean(this.props.selection[item.blobref]),
@ -179,15 +210,19 @@ cam.BlobItemContainerReact = React.createClass({
rowHeight = Math.min(rowHeight, height);
}
for (var i = 0; i < rowProps.length; i++) {
rowProps[i].size.height = rowHeight;
children.push(cam.BlobItemReact(rowProps[i]));
for (var i = startIndex; i <= endIndex; i++) {
this.childProps_[i].size.height = rowHeight;
}
return rowHeight;
},
isVisible_: function(y) {
return y >= this.state.scroll && y < (this.state.scroll + this.props.style.height);
},
handleSearchSessionChanged_: function() {
this.updateChildProps_();
this.forceUpdate();
},
@ -226,10 +261,22 @@ cam.BlobItemContainerReact = React.createClass({
},
handleScroll_: function() {
if (!this.isMounted()) {
return;
}
var scroll = this.getDOMNode().scrollTop;
this.props.history.replaceState({scroll:scroll});
this.setState({scroll:scroll});
this.fillVisibleAreaWithResults_();
},
if ((this.layoutHeight_ - scroll - this.props.style.height) > this.INFINITE_SCROLL_THRESHOLD_PX_) {
fillVisibleAreaWithResults_: function() {
if (!this.isMounted()) {
return;
}
if ((this.layoutHeight_ - this.getDOMNode().scrollTop - this.props.style.height) > this.INFINITE_SCROLL_THRESHOLD_PX_) {
return;
}