From 3d492bf450a245aafbfb5b3cff320c1d45832b37 Mon Sep 17 00:00:00 2001
From: nickago <ngonella@calpoly.edu>
Date: Tue, 11 Aug 2015 10:28:30 -0700
Subject: Completely rehauled scrolling system, finished base implementation

---
 web/react/components/post_list.jsx | 123 +++++++++++--------------------------
 1 file changed, 36 insertions(+), 87 deletions(-)

(limited to 'web/react/components/post_list.jsx')

diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 0abb3107e..cfbdad571 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -28,15 +28,13 @@ function getStateFromStores() {
 
 module.exports = React.createClass({
     displayName: "PostList",
-    scrollPosition: 0,
-    preventScrollTrigger: false,    // Can't think of an intuitive name, but when true,
-                                    // scrollPosition is affected by the 'on scroll' event below
-    gotMorePosts: false,
-    oldScrollHeight: 0,
     oldZoom: 0,
-    scrolledToNew: false,           // Signifies we've already scrolled to the "new message" bar
-                                    // one time, don't want to do it again even if the bar
-                                    // doesn't go away
+    holdPosition: false,            // The default state is to scroll to the bottom on change
+                                    // This behavior should NOT be taken advantage of, always set this to the desired state
+
+    scrollHeightFromBottom: 0,      // This represents the distance from the container's scrollHeight
+                                    // and current scrollTop
+                                    // Based on equation scrollTop + scrollHeightFromBottom = scrollHeight
     componentDidMount: function() {
 
         // Add CSS required to have the theme colors
@@ -66,58 +64,10 @@ module.exports = React.createClass({
         UserStore.addStatusesChangeListener(this._onTimeChange);
         SocketStore.addChangeListener(this._onSocketChange);
 
-        // Initialize perfect scrollbar
-        $(".post-list-holder-by-time").perfectScrollbar();
-
-        this.resize();
-
-        // Set initial values of these component properties, will be updated on every re-render
-        // scrollPosition - scroll point of bottom of current viewport
-        // oldScrollHeight - bottom scroll point possible of viewport
-        // oldZoom - represents "browser zoom"
-        var post_holder = $(".post-list-holder-by-time")[0];
-        this.scrollPosition = $(post_holder).scrollTop() + $(post_holder).innerHeight();
-        this.oldScrollHeight = post_holder.scrollHeight;
-        this.oldZoom = (window.outerWidth - 8) / window.innerWidth;
-
-        /*************** End of 'run on mount' code, Start of event listeners *****************/
-
-        var self = this;
-        $(window).resize(function(){
-            $(post_holder).perfectScrollbar('update');
-
-            // this only kind of works, detecting zoom in browsers is a nightmare
-            var newZoom = (window.outerWidth - 8) / window.innerWidth;
-
-            // We resize if :
-            // 1. We are scrolled below the max scroll
-            // 2. Edge case similar to offscreen resizing
-            // 3. We changed zoom sizes
-            if (self.scrollPosition >= post_holder.scrollHeight ||
-                (self.oldScrollHeight !== post_holder.scrollHeight && self.scrollPosition >= self.oldScrollHeight) ||
-                self.oldZoom !== newZoom) {
-                self.resize();
-            }
-
-            self.oldZoom = newZoom;
-
-            // Sets the size of the viewport, doesn't affect the overflowed component
-            if ($('#create_post').length > 0) {
-                var height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
-                $(".post-list-holder-by-time").css("height", height + "px");
-            }
-        });
-
-        // scrollPosistion records and holds every position that the user has been at
-        // unless preventScrollTrigger is enabled, then it skips one reading
-        // Consider storing this in localStorage instead
-        $(post_holder).scroll(function(e){
-            if (!self.preventScrollTrigger) {
-                self.scrollPosition = $(post_holder).scrollTop() + $(post_holder).innerHeight();
-            }
-            self.preventScrollTrigger = false;
-        });
+        // Timeout exists for the DOM to fully render before making changes
+        setTimeout(this.scrollWindow, 1)
 
+        // Highlight stylingx
         $('body').on('click.userpopover', function(e){
             if ($(e.target).attr('data-toggle') !== 'popover'
                 && $(e.target).parents('.popover.in').length === 0) {
@@ -149,13 +99,15 @@ module.exports = React.createClass({
                 $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
             }
         });
-
+    },
+    componentWillUpdate: function() {
+        var container = $('.post-list-holder-by-time');
+        if (this.holdPosition) {
+            this.scrollHeightFromBottom = container[0].scrollHeight - container.scrollTop();
+        }
     },
     componentDidUpdate: function() {
-        this.resize();
-        var post_holder = $(".post-list-holder-by-time")[0];
-        this.scrollPosition = $(post_holder).scrollTop() + $(post_holder).innerHeight();
-        this.oldScrollHeight = post_holder.scrollHeight;
+        this.scrollWindow()
         $('.post-list__content div .post').removeClass('post--last');
         $('.post-list__content div:last-child .post').addClass('post--last');
     },
@@ -166,34 +118,25 @@ module.exports = React.createClass({
         SocketStore.removeChangeListener(this._onSocketChange);
         $('body').off('click.userpopover');
     },
-    resize: function() {
-        var post_holder = $(".post-list-holder-by-time")[0];
-        this.preventScrollTrigger = true;
-        if (this.gotMorePosts) {
-            this.gotMorePosts = false;
-            $(post_holder).scrollTop($(post_holder).scrollTop() + (post_holder.scrollHeight-this.oldScrollHeight) );
-        } else if ($("#new_message")[0] && !this.scrolledToNew) {
-            $(post_holder).scrollTop($(post_holder).scrollTop() + $("#new_message").offset().top - 63);
-            this.scrolledToNew = true;
+    scrollWindow: function() {
+        var container = $('.post-list-holder-by-time');
+
+        // If there's a new message, this jumps the window to that
+        // If there's no new message, we check if there is a scroll position to retrieve from the previous state
+        // If we don't, then we just jump to the bottom
+        if ($('#new_message').length) {
+            container.scrollTop(container.scrollTop() + $('#new_message').offset().top - container.offset().top - $('.new-separator').height());
+        } else if (this.holdPosition) {
+            container.scrollTop(container[0].scrollHeight - this.scrollHeightFromBottom);
+            this.holdPosition = false;
         } else {
-            $(post_holder).scrollTop(post_holder.scrollHeight);
+            container.scrollTop(container[0].scrollHeight - container.innerHeight());
         }
-        $(post_holder).perfectScrollbar('update');
     },
     _onChange: function() {
         var newState = getStateFromStores();
 
         if (!utils.areStatesEqual(newState, this.state)) {
-            if (this.state.post_list &&
-                this.state.post_list.order &&
-                this.state.channel.id === newState.channel.id &&
-                this.state.post_list.order.length !== newState.post_list.order.length &&
-                newState.post_list.order.length > Constants.POST_CHUNK_SIZE) {
-                this.gotMorePosts = true;
-            }
-            if (this.state.channel.id !== newState.channel.id) {
-                this.scrolledToNew = false;
-            }
             this.setState(newState);
         }
     },
@@ -209,6 +152,8 @@ module.exports = React.createClass({
                 post_list.order.unshift(post.id);
             }
 
+            this.holdPosition = false;
+
             if (this.state.channel.id === msg.channel_id) {
                 this.setState({ post_list: post_list });
             };
@@ -225,6 +170,8 @@ module.exports = React.createClass({
                 post_list.posts[post.id] = post;
                 this.setState({ post_list: post_list });
 
+                this.holdPosition = true;
+
                 PostStore.storePosts(msg.channel_id, post_list);
             } else {
                 AsyncClient.getPosts(true, msg.channel_id);
@@ -241,6 +188,8 @@ module.exports = React.createClass({
                 var index = post_list.order.indexOf(msg.props.post_id);
                 if (index > -1) post_list.order.splice(index, 1);
 
+                this.holdPosition = true;
+
                 this.setState({ post_list: post_list });
 
                 PostStore.storePosts(msg.channel_id, post_list);
@@ -274,7 +223,7 @@ module.exports = React.createClass({
         $(this.refs.loadmore.getDOMNode()).text("Retrieving more messages...");
 
         var self = this;
-        var currentPos = $(".post-list").scrollTop;
+        this.holdPosition = true;
 
         Client.getPosts(
                 channel_id,
@@ -282,6 +231,7 @@ module.exports = React.createClass({
                 Constants.POST_CHUNK_SIZE,
                 function(data) {
                     $(self.refs.loadmore.getDOMNode()).text("Load more messages");
+                    console.log("here")
 
                     if (!data) return;
 
@@ -298,7 +248,6 @@ module.exports = React.createClass({
                     });
 
                     Client.getProfiles();
-                    $(".post-list").scrollTop(currentPos);
                 },
                 function(err) {
                     $(self.refs.loadmore.getDOMNode()).text("Load more messages");
-- 
cgit v1.2.3-1-g7c22

