From 5c0680397de9d3cfd0018743e0b1d58dfbdca25d Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 8 Sep 2015 10:11:57 -0400 Subject: Add post list container to hold mounted post lists for faster rendering/channel switching. --- web/react/components/channel_header.jsx | 7 +- web/react/components/channel_loader.jsx | 60 +++++++- web/react/components/post_list.jsx | 202 ++++++++++++++------------- web/react/components/post_list_container.jsx | 62 ++++++++ 4 files changed, 227 insertions(+), 104 deletions(-) create mode 100644 web/react/components/post_list_container.jsx (limited to 'web/react/components') diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 87b9cab04..db23a5831 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -64,9 +64,14 @@ export default class ChannelHeader extends React.Component { handleLeave() { Client.leaveChannel(this.state.channel.id, function handleLeaveSuccess() { + AppDispatcher.handleViewAction({ + type: ActionTypes.LEAVE_CHANNEL, + id: this.state.channel.id + }); + const townsquare = ChannelStore.getByName('town-square'); Utils.switchChannel(townsquare); - }, + }.bind(this), function handleLeaveError(err) { AsyncClient.dispatchError(err, 'handleLeave'); } diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 8e8ed3f73..ce6f60f87 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -17,6 +17,8 @@ export default class ChannelLoader extends React.Component { constructor(props) { super(props); + this.intervalId = null; + this.onSocketChange = this.onSocketChange.bind(this); this.state = {}; @@ -35,10 +37,12 @@ export default class ChannelLoader extends React.Component { PostStore.clearPendingPosts(); /* Set up interval functions */ - setInterval( + this.intervalId = setInterval( function pollStatuses() { AsyncClient.getStatuses(); - }, 30000); + }, + 30000 + ); /* Device tracking setup */ var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent); @@ -49,12 +53,12 @@ export default class ChannelLoader extends React.Component { /* Set up tracking for whether the window is active */ window.isActive = true; - $(window).focus(function windowFocus() { + $(window).on('focus', function windowFocus() { AsyncClient.updateLastViewedAt(); window.isActive = true; }); - $(window).blur(function windowBlur() { + $(window).on('blur', function windowBlur() { window.isActive = false; }); @@ -84,6 +88,54 @@ export default class ChannelLoader extends React.Component { Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';'); $('.team__header').addClass('theme--gray'); } + + /* Setup global mouse events */ + $('body').on('click.userpopover', function popOver(e) { + if ($(e.target).attr('data-toggle') !== 'popover' && + $(e.target).parents('.popover.in').length === 0) { + $('.user-popover').popover('hide'); + } + }); + + $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) { + if (ev.type === 'mouseenter') { + $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after'); + $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before'); + } else { + $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after'); + $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before'); + } + }); + + $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) { + if (ev.type === 'mouseenter') { + $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment'); + $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment'); + } else { + $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment'); + $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment'); + } + }); + + /* Setup modal events */ + $('.modal').on('show.bs.modal', function onShow() { + $('.modal-body').css('overflow-y', 'auto'); + $('.modal-body').css('max-height', $(window).height() * 0.7); + }); + } + componentWillUnmount() { + clearInterval(this.intervalId); + + $(window).off('focus'); + $(window).off('blur'); + + SocketStore.removeChangeListener(this.onSocketChange); + + $('body').off('click.userpopover'); + $('body').off('mouseenter mouseleave', '.post'); + $('body').off('mouseenter mouseleave', '.post.post--comment.same--root'); + + $('.modal').off('show.bs.modal'); } onSocketChange(msg) { if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) { diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 6fa87ca4a..d7c0d4862 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -18,8 +18,8 @@ var ActionTypes = Constants.ActionTypes; import {strings} from '../utils/config.js'; export default class PostList extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this.gotMorePosts = false; this.scrolled = false; @@ -27,6 +27,7 @@ export default class PostList extends React.Component { this.seenNewMessages = false; this.isUserScroll = true; this.userHasSeenNew = false; + this.loadInProgress = false; this.onChange = this.onChange.bind(this); this.onTimeChange = this.onTimeChange.bind(this); @@ -34,22 +35,19 @@ export default class PostList extends React.Component { this.createChannelIntroMessage = this.createChannelIntroMessage.bind(this); this.loadMorePosts = this.loadMorePosts.bind(this); this.loadFirstPosts = this.loadFirstPosts.bind(this); + this.activate = this.activate.bind(this); + this.deactivate = this.deactivate.bind(this); + this.resize = this.resize.bind(this); - this.state = this.getStateFromStores(); + this.state = this.getStateFromStores(props.channelId); this.state.numToDisplay = Constants.POST_CHUNK_SIZE; this.state.isFirstLoadComplete = false; } - getStateFromStores() { - var channel = ChannelStore.getCurrent(); - - if (channel == null) { - channel = {}; - } - - var postList = PostStore.getCurrentPosts(); + getStateFromStores(id) { + var postList = PostStore.getPosts(id); if (postList != null) { - var deletedPosts = PostStore.getUnseenDeletedPosts(channel.id); + var deletedPosts = PostStore.getUnseenDeletedPosts(id); if (deletedPosts && Object.keys(deletedPosts).length > 0) { for (var pid in deletedPosts) { @@ -70,7 +68,7 @@ export default class PostList extends React.Component { }); } - var pendingPostList = PostStore.getPendingPosts(channel.id); + var pendingPostList = PostStore.getPendingPosts(id); if (pendingPostList) { postList.order = pendingPostList.order.concat(postList.order); @@ -82,43 +80,42 @@ export default class PostList extends React.Component { } } - var lastViewed = Number.MAX_VALUE; - - if (ChannelStore.getCurrentMember() != null) { - lastViewed = ChannelStore.getCurrentMember().last_viewed_at; - } - return { - postList: postList, - channel: channel, - lastViewed: lastViewed + postList: postList }; } componentDidMount() { + if (this.props.isActive) { + this.activate(); + this.loadFirstPosts(this.props.channelId); + } + } + componentWillUnmount() { + this.deactivate(); + } + activate() { + this.gotMorePosts = false; + this.scrolled = false; + this.prevScrollTop = 0; + this.seenNewMessages = false; + this.isUserScroll = true; + this.userHasSeenNew = false; + + PostStore.clearUnseenDeletedPosts(this.props.channelId); PostStore.addChangeListener(this.onChange); - ChannelStore.addChangeListener(this.onChange); UserStore.addStatusesChangeListener(this.onTimeChange); SocketStore.addChangeListener(this.onSocketChange); - var postHolder = $('.post-list-holder-by-time'); - - $('.modal').on('show.bs.modal', function onShow() { - $('.modal-body').css('overflow-y', 'auto'); - $('.modal-body').css('max-height', $(window).height() * 0.7); - }); - - $(window).resize(function resize() { - if ($('#create_post').length > 0) { - var height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50; - postHolder.css('height', height + 'px'); - } + var postHolder = $(React.findDOMNode(this.refs.postlist)); + $(window).on('resize.' + this.props.channelId, function resize() { + this.resize(); if (!this.scrolled) { this.scrollToBottom(); } }.bind(this)); - postHolder.scroll(function scroll() { + postHolder.on('scroll', function scroll() { var position = postHolder.scrollTop() + postHolder.height() + 14; var bottom = postHolder[0].scrollHeight; @@ -134,41 +131,25 @@ export default class PostList extends React.Component { this.isUserScroll = true; }.bind(this)); - $('body').on('click.userpopover', function popOver(e) { - if ($(e.target).attr('data-toggle') !== 'popover' && - $(e.target).parents('.popover.in').length === 0) { - $('.user-popover').popover('hide'); - } - }); - $('.post-list__content div .post').removeClass('post--last'); $('.post-list__content div:last-child .post').addClass('post--last'); - $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) { - if (ev.type === 'mouseenter') { - $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after'); - $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before'); - } else { - $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after'); - $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before'); - } - }); - - $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) { - if (ev.type === 'mouseenter') { - $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment'); - $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment'); - } else { - $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment'); - $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment'); - } - }); + if (!this.state.isFirstLoadComplete) { + this.loadFirstPosts(this.props.channelId); + } + this.resize(); + this.onChange(); this.scrollToBottom(); - - if (this.state.channel.id != null) { - this.loadFirstPosts(this.state.channel.id); - } + } + deactivate() { + PostStore.removeChangeListener(this.onChange); + UserStore.removeStatusesChangeListener(this.onTimeChange); + SocketStore.removeChangeListener(this.onSocketChange); + $('body').off('click.userpopover'); + $(window).off('resize.' + this.props.channelId); + var postHolder = $(React.findDOMNode(this.refs.postlist)); + postHolder.off('scroll'); } componentDidUpdate(prevProps, prevState) { $('.post-list__content div .post').removeClass('post--last'); @@ -187,7 +168,7 @@ export default class PostList extends React.Component { var firstPost = posts[order[0]] || {}; var isNewPost = oldOrder.indexOf(order[0]) === -1; - if (this.state.channel.id !== prevState.channel.id) { + if (this.props.isActive && !prevProps.isActive) { this.scrollToBottom(); } else if (oldOrder.length === 0) { this.scrollToBottom(); @@ -201,37 +182,43 @@ export default class PostList extends React.Component { } else if (isNewPost && userId === firstPost.user_id && !utils.isComment(firstPost)) { - this.state.lastViewed = utils.getTimestamp(); this.scrollToBottom(true); // the user clicked 'load more messages' } else if (this.gotMorePosts) { var lastPost = oldPosts[oldOrder[prevState.numToDisplay]]; $('#' + lastPost.id)[0].scrollIntoView(); + this.gotMorePosts = false; } else { this.scrollTo(this.prevScrollTop); } } componentWillUpdate() { - var postHolder = $('.post-list-holder-by-time'); + var postHolder = $(React.findDOMNode(this.refs.postlist)); this.prevScrollTop = postHolder.scrollTop(); } - componentWillUnmount() { - PostStore.removeChangeListener(this.onChange); - ChannelStore.removeChangeListener(this.onChange); - UserStore.removeStatusesChangeListener(this.onTimeChange); - SocketStore.removeChangeListener(this.onSocketChange); - $('body').off('click.userpopover'); - $('.modal').off('show.bs.modal'); + componentWillReceiveProps(nextProps) { + if (nextProps.isActive === true && this.props.isActive === false) { + this.activate(); + } else if (nextProps.isActive === false && this.props.isActive === true) { + this.deactivate(); + } + } + resize() { + const postHolder = $(React.findDOMNode(this.refs.postlist)); + if ($('#create_post').length > 0) { + const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50; + postHolder.css('height', height + 'px'); + } } scrollTo(val) { this.isUserScroll = false; - var postHolder = $('.post-list-holder-by-time'); + var postHolder = $(React.findDOMNode(this.refs.postlist)); postHolder[0].scrollTop = val; } scrollToBottom(force) { this.isUserScroll = false; - var postHolder = $('.post-list-holder-by-time'); + var postHolder = $(React.findDOMNode(this.refs.postlist)); if ($('#new_message')[0] && !this.userHasSeenNew && !force) { $('#new_message')[0].scrollIntoView(); } else { @@ -241,34 +228,32 @@ export default class PostList extends React.Component { } } loadFirstPosts(id) { + if (this.loadInProgress) { + return; + } + + if (this.props.channelId == null) { + return; + } + + this.loadInProgress = true; Client.getPosts( id, PostStore.getLatestUpdate(id), function success() { + this.loadInProgress = false; this.setState({isFirstLoadComplete: true}); }.bind(this), function fail() { + this.loadInProgress = false; this.setState({isFirstLoadComplete: true}); }.bind(this) ); } onChange() { - var newState = this.getStateFromStores(); - - // Special case where the channel wasn't yet set in componentDidMount - if (!this.state.isFirstLoadComplete && this.state.channel.id == null && newState.channel.id != null) { - this.loadFirstPosts(newState.channel.id); - } - - if (!utils.areStatesEqual(newState, this.state)) { - if (this.state.channel.id !== newState.channel.id) { - PostStore.clearUnseenDeletedPosts(this.state.channel.id); - this.userHasSeenNew = false; - newState.numToDisplay = Constants.POST_CHUNK_SIZE; - } else { - newState.lastViewed = this.state.lastViewed; - } + var newState = this.getStateFromStores(this.props.channelId); + if (!utils.areStatesEqual(newState.postList, this.state.postList)) { this.setState(newState); } } @@ -424,7 +409,7 @@ export default class PostList extends React.Component { } } - var members = ChannelStore.getCurrentExtraInfo().members; + var members = ChannelStore.getExtraInfo(channel.id).members; for (var i = 0; i < members.length; i++) { if (members[i].roles.indexOf('admin') > -1) { return members[i].username; @@ -488,6 +473,11 @@ export default class PostList extends React.Component { var userId = UserStore.getCurrentId(); var renderedLastViewed = false; + var lastViewed = Number.MAX_VALUE; + + if (ChannelStore.getMember(this.props.channelId) != null) { + lastViewed = ChannelStore.getMember(this.props.channelId).last_viewed_at; + } var numToDisplay = this.state.numToDisplay; if (order.length - 1 < numToDisplay) { @@ -543,7 +533,7 @@ export default class PostList extends React.Component { ); } - if (post.user_id !== userId && post.create_at > this.state.lastViewed && !renderedLastViewed) { + if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) { renderedLastViewed = true; // Temporary fix to solve ie10/11 rendering issue @@ -577,7 +567,7 @@ export default class PostList extends React.Component { var posts = this.state.postList.posts; var order = this.state.postList.order; - var channelId = this.state.channel.id; + var channelId = this.props.channelId; $(React.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...'); @@ -619,7 +609,7 @@ export default class PostList extends React.Component { render() { var order = []; var posts; - var channel = this.state.channel; + var channel = ChannelStore.get(this.props.channelId); if (this.state.postList != null) { posts = this.state.postList.posts; @@ -628,7 +618,7 @@ export default class PostList extends React.Component { var moreMessages =

Beginning of Channel

; if (channel != null) { - if (order.length > this.state.numToDisplay) { + if (order.length >= this.state.numToDisplay) { moreMessages = ( ); } + var activeClass = ''; + if (!this.props.isActive) { + activeClass = 'inactive'; + } + return (
@@ -670,3 +665,12 @@ export default class PostList extends React.Component { ); } } + +PostList.defaultProps = { + isActive: false, + channelId: null +}; +PostList.propTypes = { + isActive: React.PropTypes.bool, + channelId: React.PropTypes.string +}; diff --git a/web/react/components/post_list_container.jsx b/web/react/components/post_list_container.jsx new file mode 100644 index 000000000..0815ac883 --- /dev/null +++ b/web/react/components/post_list_container.jsx @@ -0,0 +1,62 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +const PostList = require('./post_list.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); + +export default class PostListContainer extends React.Component { + constructor() { + super(); + + this.onChange = this.onChange.bind(this); + this.onLeave = this.onLeave.bind(this); + + let currentChannelId = ChannelStore.getCurrentId(); + if (currentChannelId) { + this.state = {currentChannelId: currentChannelId, postLists: [currentChannelId]}; + } else { + this.state = {currentChannelId: null, postLists: []}; + } + } + componentDidMount() { + ChannelStore.addChangeListener(this.onChange); + ChannelStore.addLeaveListener(this.onLeave); + } + onChange() { + let channelId = ChannelStore.getCurrentId(); + if (channelId === this.state.currentChannelId) { + return; + } + + let postLists = this.state.postLists; + if (postLists.indexOf(channelId) === -1) { + postLists.push(channelId); + } + this.setState({currentChannelId: channelId, postLists: postLists}); + } + onLeave(id) { + let postLists = this.state.postLists; + var index = postLists.indexOf(id); + if (index !== -1) { + postLists.splice(index, 1); + } + } + render() { + let postLists = this.state.postLists; + let channelId = this.state.currentChannelId; + + let postListCtls = []; + for (let i = 0; i <= this.state.postLists.length - 1; i++) { + postListCtls.push( + + ); + } + + return ( +
{postListCtls}
+ ); + } +} -- cgit v1.2.3-1-g7c22