about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-09-22 20:58:35 +0200
committerEugen Rochko <eugen@zeonfederated.com>2016-09-22 20:58:35 +0200
commit2a84271e85564928c4b5e241d7d3bde69fef40ed (patch)
treec5db43d46ba3eb056e2dd786887503f5c3c23be5 /app
parent4a670780f0fda6891f1332e7a00c4b8f925634ed (diff)
Infinite scroll for account timelines
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/components/actions/accounts.jsx55
-rw-r--r--app/assets/javascripts/components/features/account/index.jsx32
-rw-r--r--app/assets/javascripts/components/reducers/notifications.jsx4
-rw-r--r--app/assets/javascripts/components/reducers/timelines.jsx16
4 files changed, 89 insertions, 18 deletions
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
index aedc08a1d..ac9f5962f 100644
--- a/app/assets/javascripts/components/actions/accounts.jsx
+++ b/app/assets/javascripts/components/actions/accounts.jsx
@@ -1,4 +1,5 @@
-import api from '../api'
+import api   from '../api'
+import axios from 'axios';
 
 export const ACCOUNT_SET_SELF = 'ACCOUNT_SET_SELF';
 
@@ -18,6 +19,10 @@ export const ACCOUNT_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST';
 export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS';
 export const ACCOUNT_TIMELINE_FETCH_FAIL    = 'ACCOUNT_TIMELINE_FETCH_FAIL';
 
+export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST';
+export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS';
+export const ACCOUNT_TIMELINE_EXPAND_FAIL    = 'ACCOUNT_TIMELINE_EXPAND_FAIL';
+
 export function setAccountSelf(account) {
   return {
     type: ACCOUNT_SET_SELF,
@@ -27,10 +32,12 @@ export function setAccountSelf(account) {
 
 export function fetchAccount(id) {
   return (dispatch, getState) => {
+    const boundApi = api(getState);
+
     dispatch(fetchAccountRequest(id));
 
-    api(getState).get(`/api/accounts/${id}`).then(response => {
-      dispatch(fetchAccountSuccess(response.data));
+    axios.all([boundApi.get(`/api/accounts/${id}`), boundApi.get(`/api/accounts/relationships?id=${id}`)]).then(values => {
+      dispatch(fetchAccountSuccess(values[0].data, values[1].data[0]));
     }).catch(error => {
       dispatch(fetchAccountFail(id, error));
     });
@@ -49,6 +56,20 @@ export function fetchAccountTimeline(id) {
   };
 };
 
+export function expandAccountTimeline(id) {
+  return (dispatch, getState) => {
+    const lastId = getState().getIn(['timelines', 'accounts_timelines', id]).last();
+
+    dispatch(expandAccountTimelineRequest(id));
+
+    api(getState).get(`/api/accounts/${id}/statuses?max_id=${lastId}`).then(response => {
+      dispatch(expandAccountTimelineSuccess(id, response.data));
+    }).catch(error => {
+      dispatch(expandAccountTimelineFail(id, error));
+    });
+  };
+};
+
 export function fetchAccountRequest(id) {
   return {
     type: ACCOUNT_FETCH_REQUEST,
@@ -56,10 +77,11 @@ export function fetchAccountRequest(id) {
   };
 };
 
-export function fetchAccountSuccess(account) {
+export function fetchAccountSuccess(account, relationship) {
   return {
     type: ACCOUNT_FETCH_SUCCESS,
-    account: account
+    account: account,
+    relationship: relationship
   };
 };
 
@@ -159,3 +181,26 @@ export function fetchAccountTimelineFail(id, error) {
     error: error
   };
 };
+
+export function expandAccountTimelineRequest(id) {
+  return {
+    type: ACCOUNT_TIMELINE_EXPAND_REQUEST,
+    id: id
+  };
+};
+
+export function expandAccountTimelineSuccess(id, statuses) {
+  return {
+    type: ACCOUNT_TIMELINE_EXPAND_SUCCESS,
+    id: id,
+    statuses: statuses
+  };
+};
+
+export function expandAccountTimelineFail(id, error) {
+  return {
+    type: ACCOUNT_TIMELINE_EXPAND_FAIL,
+    id: id,
+    error: error
+  };
+};
diff --git a/app/assets/javascripts/components/features/account/index.jsx b/app/assets/javascripts/components/features/account/index.jsx
index edbc34826..46d0e5954 100644
--- a/app/assets/javascripts/components/features/account/index.jsx
+++ b/app/assets/javascripts/components/features/account/index.jsx
@@ -1,13 +1,19 @@
-import { connect }                                                            from 'react-redux';
-import PureRenderMixin                                                        from 'react-addons-pure-render-mixin';
-import ImmutablePropTypes                                                     from 'react-immutable-proptypes';
-import { fetchAccount, followAccount, unfollowAccount, fetchAccountTimeline } from '../../actions/accounts';
-import { replyCompose }                                                       from '../../actions/compose';
-import { favourite, reblog }                                                  from '../../actions/interactions';
-import Header                                                                 from './components/header';
-import { selectStatus }                                                       from '../../reducers/timelines';
-import StatusList                                                             from '../../components/status_list';
-import Immutable                                                              from 'immutable';
+import { connect }           from 'react-redux';
+import PureRenderMixin       from 'react-addons-pure-render-mixin';
+import ImmutablePropTypes    from 'react-immutable-proptypes';
+import {
+  fetchAccount,
+  followAccount,
+  unfollowAccount,
+  fetchAccountTimeline,
+  expandAccountTimeline
+}                            from '../../actions/accounts';
+import { replyCompose }      from '../../actions/compose';
+import { favourite, reblog } from '../../actions/interactions';
+import Header                from './components/header';
+import { selectStatus }      from '../../reducers/timelines';
+import StatusList            from '../../components/status_list';
+import Immutable             from 'immutable';
 
 function selectAccount(state, id) {
   return state.getIn(['timelines', 'accounts', id], null);
@@ -65,6 +71,10 @@ const Account = React.createClass({
     this.props.dispatch(favourite(status));
   },
 
+  handleScrollToBottom () {
+    this.props.dispatch(expandAccountTimeline(this.props.account.get('id')));
+  },
+
   render () {
     const { account, statuses } = this.props;
 
@@ -75,7 +85,7 @@ const Account = React.createClass({
     return (
       <div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}>
         <Header account={account} onFollow={this.handleFollow} onUnfollow={this.handleUnfollow} />
-        <StatusList statuses={statuses} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} />
+        <StatusList statuses={statuses} onScrollToBottom={this.handleScrollToBottom} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} />
       </div>
     );
   }
diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx
index 47641557d..995947b2d 100644
--- a/app/assets/javascripts/components/reducers/notifications.jsx
+++ b/app/assets/javascripts/components/reducers/notifications.jsx
@@ -10,7 +10,8 @@ import {
   ACCOUNT_FETCH_FAIL,
   ACCOUNT_FOLLOW_FAIL,
   ACCOUNT_UNFOLLOW_FAIL,
-  ACCOUNT_TIMELINE_FETCH_FAIL
+  ACCOUNT_TIMELINE_FETCH_FAIL,
+  ACCOUNT_TIMELINE_EXPAND_FAIL
 }                                                   from '../actions/accounts';
 import { STATUS_FETCH_FAIL }                        from '../actions/statuses';
 import Immutable                                    from 'immutable';
@@ -48,6 +49,7 @@ export default function notifications(state = initialState, action) {
     case ACCOUNT_FOLLOW_FAIL:
     case ACCOUNT_UNFOLLOW_FAIL:
     case ACCOUNT_TIMELINE_FETCH_FAIL:
+    case ACCOUNT_TIMELINE_EXPAND_FAIL:
     case STATUS_FETCH_FAIL:
       return notificationFromError(state, action.error);
     case NOTIFICATION_DISMISS:
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
index e3de9e9b2..8dd9c3b1f 100644
--- a/app/assets/javascripts/components/reducers/timelines.jsx
+++ b/app/assets/javascripts/components/reducers/timelines.jsx
@@ -13,7 +13,8 @@ import {
   ACCOUNT_FETCH_SUCCESS,
   ACCOUNT_FOLLOW_SUCCESS,
   ACCOUNT_UNFOLLOW_SUCCESS,
-  ACCOUNT_TIMELINE_FETCH_SUCCESS
+  ACCOUNT_TIMELINE_FETCH_SUCCESS,
+  ACCOUNT_TIMELINE_EXPAND_SUCCESS
 }                                from '../actions/accounts';
 import { STATUS_FETCH_SUCCESS }  from '../actions/statuses';
 import { FOLLOW_SUBMIT_SUCCESS } from '../actions/follow';
@@ -110,6 +111,17 @@ function normalizeAccountTimeline(state, accountId, statuses) {
   return state;
 };
 
+function appendNormalizedAccountTimeline(state, accountId, statuses) {
+  let moreIds = Immutable.List();
+
+  statuses.forEach((status, i) => {
+    state   = normalizeStatus(state, status);
+    moreIds = moreIds.set(i, status.get('id'));
+  });
+
+  return state.updateIn(['accounts_timelines', accountId], Immutable.List(), list => list.push(...moreIds));
+};
+
 function updateTimeline(state, timeline, status) {
   state = normalizeStatus(state, status);
   state = state.update(timeline, list => list.unshift(status.get('id')));
@@ -176,6 +188,8 @@ export default function timelines(state = initialState, action) {
       return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
     case ACCOUNT_TIMELINE_FETCH_SUCCESS:
       return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
+    case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
+      return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
     default:
       return state;
   }