Infinite scroll for account timelines
This commit is contained in:
		
							parent
							
								
									4a670780f0
								
							
						
					
					
						commit
						2a84271e85
					
				| @ -1,4 +1,5 @@ | |||||||
| import api from '../api' | import api   from '../api' | ||||||
|  | import axios from 'axios'; | ||||||
| 
 | 
 | ||||||
| export const ACCOUNT_SET_SELF = 'ACCOUNT_SET_SELF'; | 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_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS'; | ||||||
| export const ACCOUNT_TIMELINE_FETCH_FAIL    = 'ACCOUNT_TIMELINE_FETCH_FAIL'; | 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) { | export function setAccountSelf(account) { | ||||||
|   return { |   return { | ||||||
|     type: ACCOUNT_SET_SELF, |     type: ACCOUNT_SET_SELF, | ||||||
| @ -27,10 +32,12 @@ export function setAccountSelf(account) { | |||||||
| 
 | 
 | ||||||
| export function fetchAccount(id) { | export function fetchAccount(id) { | ||||||
|   return (dispatch, getState) => { |   return (dispatch, getState) => { | ||||||
|  |     const boundApi = api(getState); | ||||||
|  | 
 | ||||||
|     dispatch(fetchAccountRequest(id)); |     dispatch(fetchAccountRequest(id)); | ||||||
| 
 | 
 | ||||||
|     api(getState).get(`/api/accounts/${id}`).then(response => { |     axios.all([boundApi.get(`/api/accounts/${id}`), boundApi.get(`/api/accounts/relationships?id=${id}`)]).then(values => { | ||||||
|       dispatch(fetchAccountSuccess(response.data)); |       dispatch(fetchAccountSuccess(values[0].data, values[1].data[0])); | ||||||
|     }).catch(error => { |     }).catch(error => { | ||||||
|       dispatch(fetchAccountFail(id, 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) { | export function fetchAccountRequest(id) { | ||||||
|   return { |   return { | ||||||
|     type: ACCOUNT_FETCH_REQUEST, |     type: ACCOUNT_FETCH_REQUEST, | ||||||
| @ -56,10 +77,11 @@ export function fetchAccountRequest(id) { | |||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function fetchAccountSuccess(account) { | export function fetchAccountSuccess(account, relationship) { | ||||||
|   return { |   return { | ||||||
|     type: ACCOUNT_FETCH_SUCCESS, |     type: ACCOUNT_FETCH_SUCCESS, | ||||||
|     account: account |     account: account, | ||||||
|  |     relationship: relationship | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -159,3 +181,26 @@ export function fetchAccountTimelineFail(id, error) { | |||||||
|     error: 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 | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,13 +1,19 @@ | |||||||
| import { connect }                                                            from 'react-redux'; | import { connect }           from 'react-redux'; | ||||||
| import PureRenderMixin                                                        from 'react-addons-pure-render-mixin'; | import PureRenderMixin       from 'react-addons-pure-render-mixin'; | ||||||
| import ImmutablePropTypes                                                     from 'react-immutable-proptypes'; | import ImmutablePropTypes    from 'react-immutable-proptypes'; | ||||||
| import { fetchAccount, followAccount, unfollowAccount, fetchAccountTimeline } from '../../actions/accounts'; | import { | ||||||
| import { replyCompose }                                                       from '../../actions/compose'; |   fetchAccount, | ||||||
| import { favourite, reblog }                                                  from '../../actions/interactions'; |   followAccount, | ||||||
| import Header                                                                 from './components/header'; |   unfollowAccount, | ||||||
| import { selectStatus }                                                       from '../../reducers/timelines'; |   fetchAccountTimeline, | ||||||
| import StatusList                                                             from '../../components/status_list'; |   expandAccountTimeline | ||||||
| import Immutable                                                              from 'immutable'; | }                            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) { | function selectAccount(state, id) { | ||||||
|   return state.getIn(['timelines', 'accounts', id], null); |   return state.getIn(['timelines', 'accounts', id], null); | ||||||
| @ -65,6 +71,10 @@ const Account = React.createClass({ | |||||||
|     this.props.dispatch(favourite(status)); |     this.props.dispatch(favourite(status)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|  |   handleScrollToBottom () { | ||||||
|  |     this.props.dispatch(expandAccountTimeline(this.props.account.get('id'))); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { account, statuses } = this.props; |     const { account, statuses } = this.props; | ||||||
| 
 | 
 | ||||||
| @ -75,7 +85,7 @@ const Account = React.createClass({ | |||||||
|     return ( |     return ( | ||||||
|       <div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}> |       <div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}> | ||||||
|         <Header account={account} onFollow={this.handleFollow} onUnfollow={this.handleUnfollow} /> |         <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> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -10,7 +10,8 @@ import { | |||||||
|   ACCOUNT_FETCH_FAIL, |   ACCOUNT_FETCH_FAIL, | ||||||
|   ACCOUNT_FOLLOW_FAIL, |   ACCOUNT_FOLLOW_FAIL, | ||||||
|   ACCOUNT_UNFOLLOW_FAIL, |   ACCOUNT_UNFOLLOW_FAIL, | ||||||
|   ACCOUNT_TIMELINE_FETCH_FAIL |   ACCOUNT_TIMELINE_FETCH_FAIL, | ||||||
|  |   ACCOUNT_TIMELINE_EXPAND_FAIL | ||||||
| }                                                   from '../actions/accounts'; | }                                                   from '../actions/accounts'; | ||||||
| import { STATUS_FETCH_FAIL }                        from '../actions/statuses'; | import { STATUS_FETCH_FAIL }                        from '../actions/statuses'; | ||||||
| import Immutable                                    from 'immutable'; | import Immutable                                    from 'immutable'; | ||||||
| @ -48,6 +49,7 @@ export default function notifications(state = initialState, action) { | |||||||
|     case ACCOUNT_FOLLOW_FAIL: |     case ACCOUNT_FOLLOW_FAIL: | ||||||
|     case ACCOUNT_UNFOLLOW_FAIL: |     case ACCOUNT_UNFOLLOW_FAIL: | ||||||
|     case ACCOUNT_TIMELINE_FETCH_FAIL: |     case ACCOUNT_TIMELINE_FETCH_FAIL: | ||||||
|  |     case ACCOUNT_TIMELINE_EXPAND_FAIL: | ||||||
|     case STATUS_FETCH_FAIL: |     case STATUS_FETCH_FAIL: | ||||||
|       return notificationFromError(state, action.error); |       return notificationFromError(state, action.error); | ||||||
|     case NOTIFICATION_DISMISS: |     case NOTIFICATION_DISMISS: | ||||||
|  | |||||||
| @ -13,7 +13,8 @@ import { | |||||||
|   ACCOUNT_FETCH_SUCCESS, |   ACCOUNT_FETCH_SUCCESS, | ||||||
|   ACCOUNT_FOLLOW_SUCCESS, |   ACCOUNT_FOLLOW_SUCCESS, | ||||||
|   ACCOUNT_UNFOLLOW_SUCCESS, |   ACCOUNT_UNFOLLOW_SUCCESS, | ||||||
|   ACCOUNT_TIMELINE_FETCH_SUCCESS |   ACCOUNT_TIMELINE_FETCH_SUCCESS, | ||||||
|  |   ACCOUNT_TIMELINE_EXPAND_SUCCESS | ||||||
| }                                from '../actions/accounts'; | }                                from '../actions/accounts'; | ||||||
| import { STATUS_FETCH_SUCCESS }  from '../actions/statuses'; | import { STATUS_FETCH_SUCCESS }  from '../actions/statuses'; | ||||||
| import { FOLLOW_SUBMIT_SUCCESS } from '../actions/follow'; | import { FOLLOW_SUBMIT_SUCCESS } from '../actions/follow'; | ||||||
| @ -110,6 +111,17 @@ function normalizeAccountTimeline(state, accountId, statuses) { | |||||||
|   return state; |   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) { | function updateTimeline(state, timeline, status) { | ||||||
|   state = normalizeStatus(state, status); |   state = normalizeStatus(state, status); | ||||||
|   state = state.update(timeline, list => list.unshift(status.get('id'))); |   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)); |       return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants)); | ||||||
|     case ACCOUNT_TIMELINE_FETCH_SUCCESS: |     case ACCOUNT_TIMELINE_FETCH_SUCCESS: | ||||||
|       return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses)); |       return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses)); | ||||||
|  |     case ACCOUNT_TIMELINE_EXPAND_SUCCESS: | ||||||
|  |       return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses)); | ||||||
|     default: |     default: | ||||||
|       return state; |       return state; | ||||||
|   } |   } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user