Infinite scroll for account timelines

This commit is contained in:
Eugen Rochko 2016-09-22 20:58:35 +02:00
parent 4a670780f0
commit 2a84271e85
4 changed files with 89 additions and 18 deletions

View File

@ -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
};
};

View File

@ -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>
); );
} }

View File

@ -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:

View File

@ -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;
} }